C++ 为什么我们可以在 `const` 对象上使用 `std::move`?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28595117/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 21:01:23  来源:igfitidea点击:

Why can we use `std::move` on a `const` object?

c++c++11

提问by camino

In C++11, we can write this code:

在 C++11 中,我们可以编写以下代码:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

when I call std::move, it means I want to move the object, i.e. I will change the object. To move a constobject is unreasonable, so why does std::movenot restrict this behaviour? It will be a trap in the future, right?

当我调用 时std::move,表示我要移动对象,即我将更改对象。移动一个const物体是不合理的,那么为什么不std::move限制这种行为呢?将来会是陷阱吧?

Here trap means as Brandon mentioned in the comment:

这里陷阱的意思是布兰登在评论中提到的:

" I think he means it "traps" him sneaky sneaky because if he doesn't realize, he ends up with a copy which is not what he intended."

“我认为他的意思是它“诱骗”了他,因为如果他没有意识到,他最终会得到一份不是他想要的副本。”

In the book 'Effective Modern C++' by Scott Meyers, he gives an example:

在 Scott Meyers 所著的《Effective Modern C++》一书中,他举了一个例子:

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

If std::movewas forbidden from operating on a constobject, we could easily find out the bug, right?

如果std::move禁止对const对象进行操作,我们很容易找出错误,对吗?

采纳答案by Yakk - Adam Nevraumont

struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

here we see a use of std::moveon a T const. It returns a T const&&. We have a move constructor for strangethat takes exactly this type.

在这里,我们看到了std::moveon a的使用T const。它返回一个T const&&. 我们有一个移动构造函数strange,它正好采用这种类型。

And it is called.

它被称为。

Now, it is true that this strange type is more rare than the bugs your proposal would fix.

现在,确实这种奇怪的类型比您的提案要修复的错误更罕见。

But, on the other hand, the existing std::moveworks better in generic code, where you don't know if the type you are working with is a Tor a T const.

但是,另一方面,现有的std::move在泛型代码中效果更好,您不知道正在使用的类型是 aT还是 a T const

回答by Mooing Duck

There's a trick here you're overlooking, namely that std::move(cat)doesn't actually move anything. It merely tells the compiler to tryto move. However, since your class has no constructor that accepts a const CAT&&, it will instead use the implicit const CAT&copy constructor, and safely copy. No danger, no trap. If the copy constructor is disabled for any reason, you'll get a compiler error.

您忽略了一个技巧,即std::move(cat)实际上并没有移动任何东西。它只是告诉编译器尝试移动。但是,由于您的类没有接受 a 的构造函数,const CAT&&它将改为使用隐式const CAT&复制构造函数,并安全地复制。没有危险,没有陷阱。如果出于任何原因禁用了复制构造函数,您将收到编译器错误。

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

prints COPY, not MOVE.

打印COPY,不是MOVE

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Note that the bug in the code you mention is a performanceissue, not a stabilityissue, so such a bug won't cause a crash, ever. It will just use a slower copy. Additionally, such a bug also occurs for non-const objects that don't have move constructors, so merely adding a constoverload won't catch all of them. We could check for the ability to move construct or move assign from the parameter type, but that would interfere with generic template code that is supposedto fall back on the copy constructor. And heck, maybe someone wants to be able to construct from const CAT&&, who am I to say he can't?

请注意,您提到的代码中的错误是性能问题,而不是稳定性问题,因此此类错误永远不会导致崩溃。它只会使用较慢的副本。此外,对于没有移动构造函数的非常量对象也会出现这样的错误,因此仅添加const重载不会捕获所有这些。我们可以检查从参数类型移动构造或移动赋值的能力,但这会干扰应该回退到复制构造函数的通用模板代码。哎呀,也许有人希望能够从 构建const CAT&&,我能说他不能吗?

回答by Howard Hinnant

One reason the rest of the answers have overlooked so far is the ability for genericcode to be resilient in the face of move. For example lets say that I wanted to write a generic function which moved all of the elements out of one kind of container to create another kind of container with the same values:

到目前为止,其余答案都忽略的一个原因是通用代码在面对移动时具有弹性的能力。例如,假设我想编写一个通用函数,将所有元素从一种容器中移出,以创建另一种具有相同值的容器:

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Cool, now I can relatively efficiently create a vector<string>from a deque<string>and each individual stringwill be moved in the process.

很酷,现在我可以相对有效地vector<string>从 a创建一个,deque<string>并且每个人string都会在这个过程中被感动。

But what if I want to move from a map?

但是如果我想从 a 移动map呢?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

If std::moveinsisted on a non-constargument, the above instantiation of move_eachwould not compile because it is trying to move a const int(the key_typeof the map). But this code doesn't careif it can't move the key_type. It wants to move the mapped_type(std::string) for performance reasons.

如果std::move坚持一个非const参数,上面的实例化move_each将无法编译,因为它试图移动 a const int( key_typethe map)。但是这段代码不在乎它是否不能移动key_type. 出于性能原因,它想移动mapped_type( std::string)。

It is for this example, and countless other examples like it in generic coding that std::moveis a request to move, not a demand to move.

正是对于这个例子,以及无数其他类似在通用编码中的例子,它std::move请求移动,而不是移动需求。

回答by Bogeegee

I have the same concern as the OP.

我和 OP 有同样的担忧。

std::move does not move an object, neither guarantees the object is movable. Then why is it called move?

std::move 不移动对象,也不保证对象是可移动的。那为什么叫动呢?

I think being not movable can be one of following two scenarios:

我认为不可移动可能是以下两种情况之一:

1. The moving type is const.

1.移动类型是const。

The reason we have const keyword in the language is that we want the compiler to prevent any change to an object defined to be const. Given the example in Scott Meyers' book:

我们在语言中使用 const 关键字的原因是我们希望编译器防止对定义为 const 的对象进行任何更改。以 Scott Meyers 书中的例子为例:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     { … } // doesn't do what it seems to!    
     …
    private:
     std::string value;
    };

What does it literally mean? Move a const string to the value member - at least, that's my understanding before I reading the explanation.

它的字面意思是什么?将 const 字符串移动到 value 成员 - 至少,这是我在阅读解释之前的理解。

If the language intends to not do move or not guarantee move is applicable when std::move() is called, then it is literally misleading when using word move.

如果该语言打算在调用 std::move() 时不执行 move 或不保证 move 适用,那么在使用单词 move 时它实际上是误导性的。

If the language is encouraging people using std::move to have better efficiency, it has to prevent traps like this as early as possible, especially for this type of obvious literal contradiction.

如果该语言鼓励人们使用 std::move 提高效率,则必须尽早防止此类陷阱,尤其是对于这种明显的字面矛盾。

I agree that people should be aware moving a constant is impossible, but this obligation should not imply the compiler can be silent when obvious contradiction happens.

我同意人们应该意识到移动常量是不可能的,但是这种义务不应该意味着编译器在发生明显的矛盾时可以保持沉默。

2. The object has no move constructor

2.对象没有移动构造函数

Personally, I think this is a separate story from OP's concern, as Chris Drew said

就个人而言,正如 Chris Drew 所说,我认为这是一个与 OP 关注的不同的故事

@hvd That seems like a bit of a non-argument to me. Just because OP's suggestion doesn't fix all bugs in the world doesn't necessarily mean it is a bad idea (it probably is, but not for the reason you give). – Chris Drew

@hvd 对我来说这似乎是一个不争的事实。仅仅因为 OP 的建议不能修复世界上的所有错误并不一定意味着它是一个坏主意(它可能是,但不是因为你给出的原因)。— 克里斯·德鲁

回答by mlo

I'm surprised nobody mentioned the backward compatibility aspect of this. I believe, std::movewas purposely designed to do this in C++11. Imagine you're working with a legacy codebase, that heavily relies on C++98 libraries, so without the fallback on copy assignment, moving would break things.

我很惊讶没有人提到这个的向后兼容性方面。我相信,std::move是专门为在 C++11 中做到这一点而设计的。想象一下,您正在使用严重依赖 C++98 库的遗留代码库,因此如果没有复制分配的回退,移动会破坏事情。

回答by Zinovy Nis

Fortunately you can use clang-tidy's check to find such issues: https://clang.llvm.org/extra/clang-tidy/checks/performance-move-const-arg.html

幸运的是,您可以使用 clang-tidy 的检查来查找此类问题:https: //clang.llvm.org/extra/clang-tidy/checks/performance-move-const-arg.html