我看了C++Primer中解释std::move
的一个例子。示例如下:
int &&rr1 = 42;
int &&rr3 = std::move(rr1);
在对上述代码片段的解释中,写道:
调用move<告诉编译器我们有一个左值,我们想把它当作是一个右值。重要的是要认识到,调动的呼吁承诺了我们没有打算再次使用
rr1
,除非分配给它或销毁它。在调用移动后,我们无法对移动的from对象的值进行任何假设。>
我的第一个问题是:我现在可以安全地写std:: cout << rr1;
吗?由于在上面引用的段落中,我们不能对从对象移动的值做出任何假设,因此标准是否保证std::cout << rr1;
是安全的(不是UB或取决于实现等(?
这里是另一个例子,
std::string str = "abc";
auto&& str2 = std::move(str);
cout << str; // is this safe(not UB or depends on implementation)
类似地,在上面的代码段中,像cout << str;
一样使用str
的值安全吗?
如果是,那么这是Stanley的《C++入门》一书中的错误/错误吗
std::move
本身除了添加右值引用之外什么都不做,所以这两个示例都是安全的。
std::move
和通过右值引用的访问都不会对对象在核心语言中的有效性或状态产生任何特殊影响。
std::move
的目的只是用作例如函数调用的参数,作为对被调用方的承诺,即您不会关心调用后对象的状态。
例如,如果函数不修改对象的状态,那么在将对象的状态传递给具有std::move
的函数后,也可以定义使用对象的状态。在任何情况下都不存在由于std::move
调用本身而导致的未定义行为。
因此,这本书是正确的,std::move
的使用通常应被视为不依赖于调用后对象状态的承诺。只是有点不清楚,承诺只是一份合同,而不是核心语言的一部分。它是针对被调用者的,而不是针对编译器的。
但特别是标准库通常依赖于这一承诺。当调用代码的其余部分错误地依赖于标准库调用后的状态时,这可能会导致未定义的行为。由于标准库的特殊位置,在这种情况下,承诺也指向编译器。
合同的细节各不相同。例如,std::unique_ptr
在移动后提供了强有力的保证。以下定义明确,断言条件始终满足:
auto p = std::make_unique<int>(1);
auto q = std::move(p);
auto ptr = p.get();
assert(ptr == nullptr);
这只是一个标准约定,如果没有这样的特定保证,移动操作可能会导致传递对象的任何有效状态。
这两种情况都是安全的,因为不会发生移动操作(移动构造或移动分配(。例如,在auto&& str2 = std::move(str);
中,std::move(str)
只生成一个x值(右值(,然后它被绑定到引用str2
。
特别是,std::move生成一个标识其参数t的xvalue表达式。它完全等效于右值引用类型的static_cast。
另一方面,
std::string str = "abc";
auto str2 = std::move(str); // move construction happens
以下是来自cppreference.com的示例:
除非另有说明,否则所有具有从中移出的被放置在";有效但未指定的状态";,意思对象的类不变量成立(因此函数没有先决条件,如赋值运算符,可以安全地用于移动后的对象(:
std::vector<std::string> v; std::string str = "example"; v.push_back(std::move(str)); // str is now valid but unspecified str.back(); // undefined behavior if size() == 0: back() has a precondition !empty() if (!str.empty()) str.back(); // OK, empty() has no precondition and back() precondition is met str.clear(); // OK, clear() has no preconditions