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

cam*_*ino 96 c++ c++11

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

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11
Run Code Online (Sandbox Code Playgroud)

当我打电话时std::move,这意味着我想移动对象,即我将改变对象.移动const对象是不合理的,为什么不std::move限制这种行为呢?这将是未来的陷阱,对吧?

陷阱意味着布兰登在评论中提到:

"我认为他的意思是"偷偷摸摸"他鬼鬼祟祟偷偷摸摸,因为如果他没有意识到,他最终得到的副本并不是他想要的."

在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;
};
Run Code Online (Sandbox Code Playgroud)

如果std::move禁止在const对象上操作,我们可以很容易地找出错误,对吧?

Moo*_*uck 99

这里有一个你可以忽略的技巧,即它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);
}
Run Code Online (Sandbox Code Playgroud)

打印COPY,而不是MOVE.

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

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

  • 我不认为他在计算机/汇编方面意味着"陷阱".我认为他的意思是"陷阱"他鬼鬼祟祟偷偷摸摸,因为如果他没有意识到,他最终得到的副本并不是他想要的.我猜.. (8认同)

Yak*_*ont 47

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);
Run Code Online (Sandbox Code Playgroud)

在这里我们看到了一个使用std::moveT const.它返回一个T const&&.我们有一个移动构造函数strange,它完全采用这种类型.

它被称为.

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

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

  • +1是第一个实际尝试解释为什么你会在`const`对象上_want_调用`std :: move`的答案. (3认同)
  • +1 用于显示采用 `const T&amp;&amp;` 的函数。这表达了一种“API 协议”,即“我将采用右值引用,但我保证不会修改它”。我想,除了使用可变的时候,这种情况并不常见。也许另一个用例是能够在几乎任何东西上使用“forward_as_tuple”,然后再使用它。 (2认同)

How*_*ant 20

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

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

很酷,现在我可以相对有效地创建一个vector<string>来自deque<string>每个人string将在此过程中移动.

但是,如果我想从一个移动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';
}
Run Code Online (Sandbox Code Playgroud)

如果std::move坚持非const参数,则上述实例化move_each将无法编译,因为它正在尝试移动const int(key_typemap).但是这段代码并不关心它是否无法移动key_type.它出于性能原因想要移动mapped_type(std::string).

就这个例子而言,无数其他例子在通用编码中std::move就是移动请求而不是移动需求.