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

C++ 为什么我们可以在“const”对象上使用“std::move”?,c++,c++11,C++,C++11,在C++11中,我们可以编写以下代码: struct Cat { Cat(){} }; const Cat cat; std::move(cat); //this is valid in C++11 当我调用std::move时,表示我想移动对象,即我将更改对象。移动const对象是不合理的,那么为什么std::move不限制这种行为呢?这将是一个陷阱在未来,对吗 这里的陷阱是指布兰登在评论中提到的: 我想他是认真的,因为如果他不这么做的话 要知道,他最终得到的不是他想要的。” 在Sc

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

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11
当我调用
std::move
时,表示我想移动对象,即我将更改对象。移动
const
对象是不合理的,那么为什么
std::move
不限制这种行为呢?这将是一个陷阱在未来,对吗

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

我想他是认真的,因为如果他不这么做的话 要知道,他最终得到的不是他想要的。”

在Scott Meyers的《有效的现代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;
};

如果
std::move
被禁止在
const
对象上操作,那么我们很容易发现错误,对吗?

这里有一个技巧,您忽略了,即
std::move(cat)
实际上不移动任何东西。它只是告诉编译器尝试移动。但是,由于您的类没有接受
常量CAT&&
的构造函数,因此它将使用隐式
常量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);
}
struct CAT
{
CAT(){}
猫(常数猫&){std::cout
这里我们看到了
std::move
T const
上的用法。它返回一个
T const&&
。我们有一个
strange
的move构造函数,它正好采用这种类型

它被称为

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


但是,另一方面,现有的
std::move
在通用代码中工作得更好,因为您不知道您使用的类型是
t
还是
t const

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

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}
如果
std::move
坚持使用非
const
参数,则上述
move\u each
的实例化将不会编译,因为它正在尝试移动
const int
(映射的
键类型
)。但此代码不关心是否无法移动
键\u类型
。出于性能原因,它希望移动
映射的\u类型
std::string


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

我和OP有同样的担忧

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

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

1.移动类型为常量。

我们在语言中使用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;
    };
它的字面意思是什么?将const字符串移动到value成员-至少,这是我在阅读解释之前的理解

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

如果语言鼓励人们使用std::move来提高效率,那么就必须尽早避免这样的陷阱,特别是对于这种明显的文字矛盾

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

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

就我个人而言,正如克里斯·德鲁所说,我认为这与OP的担忧是不同的

@hvd对我来说似乎没有什么争议。仅仅因为OP的建议没有解决世界上所有的bug并不一定意味着这是个坏主意(可能是,但不是因为你给出的原因)。–Chris Drew


我很惊讶没有人提到这方面的向后兼容性。我相信,
std::move
是专门为在C++11中实现这一点而设计的。想象一下,您使用的是传统的代码库,它严重依赖于C++98库,因此如果没有副本分配的回退,移动将破坏一切。

幸运的是,您可以使用clang-tidy检查以发现此类问题:

但尝试移动它。尝试更改其状态。
std::move
本身对对象没有任何作用。有人可能会说
std::move
名称不好。它实际上没有移动任何东西。它所做的一切都转换为右值引用。尝试
CAT cat2=std::move(CAT);
,假设
CAT
支持常规移动分配。
std::move
只是一个强制转换,实际上并不移动anything@WhozCraig:小心,因为您发布的代码编译和执行时没有任何警告,这有点误导。@MooingDuck从未说过它不会编译。它之所以有效,是因为默认的复制因子是enabled.Squelch that and the wheels off.Upticked.值得注意的是,在用户定义常规移动构造函数或赋值运算符时隐式删除复制构造函数也将通过中断编译来证明这一点。回答不错。还值得一提的是,需要非
常量
左值的复制构造函数也会获胜't help.[class.copy]§8:“否则,隐式声明的复制构造函数将具有
X::X(X&)
”我认为他在计算机/汇编术语中不是指“陷阱”。我认为他指的是“陷阱”他鬼鬼祟祟的,因为如果他没有意识到,他最终会得到一份不是他想要的。我想…你可以多投1票,我可以多投4票,一枚金徽章。;)击掌
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';
}
    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;
    };