std::move C++ 入门第 5 版中发现的错误
std::move Bug Spotted in C++ Primer 5th Edition
我看了 C++ Primer 中的一个例子来解释 std::move
。示例如下:
int &&rr1 = 42;
int &&rr3 = std::move(rr1);
上面代码片段的解释中是这样写的:
Calling move tells the compiler that we have an lvalue that we want to treat as if it
were an rvalue. It is essential to realize that the call to move promises that we do not
intend to use rr1
again except to assign to it or to destroy it. After a call to move, we
cannot make any assumptions about the value of the moved-from object.
我的第一个问题是:我现在可以安全地写 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++ Primer 一书中的 mistake/bug。
这两种情况都是安全的,因为没有移动操作(移动构造或移动赋值)发生。例如,在 auto&& str2 = std::move(str);
中,std::move(str)
仅生成一个 xvalue(右值),然后将其绑定到引用 str2
.
In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.
另一方面,
std::string str = "abc";
auto str2 = std::move(str); // move construction happens
这是来自 cppreference.com 的示例:
Unless otherwise specified, all standard library objects that have
been moved from are placed in a "valid but unspecified state", meaning
the object's class invariants hold (so functions without
preconditions, such as the assignment operator, can be safely used on
the object after it was moved from):
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
std::move
本身除了添加一个右值引用外什么都不做,所以这两个例子都是安全的。
std::move
和通过右值引用访问都不会对核心语言中对象的有效性或状态产生任何特殊影响。
std::move
的目的只是用作参数,例如函数调用,作为对被调用者的承诺,你不会关心调用后对象的状态。
如果函数例如不修改对象的状态,那么在将对象的状态传递给带有 std::move
的函数之后使用对象的状态也是明确定义的。由于 std::move
在任何情况下都会调用自身,所以没有未定义的行为。
所以这本书是正确的,std::move
的使用通常应被视为不依赖调用后对象状态的承诺。只是有点不清楚,promise 只是一个契约,不是核心语言的一部分。它是针对被调用者的,而不是针对编译器的。
但特别是标准库通常依赖于此承诺。当调用代码的其余部分错误地依赖标准库调用后的状态时,这可能会导致未定义的行为。由于标准库的特殊地位,本例中的承诺也是针对编译器的。
合同的细节有所不同。例如 std::unique_ptr
在移动后做出强有力的保证。以下是明确定义的并且始终满足断言条件:
auto p = std::make_unique<int>(1);
auto q = std::move(p);
auto ptr = p.get();
assert(ptr == nullptr);
如果没有此类特定保证,移动操作可能会导致所传递对象处于任何有效状态,这只是一个标准约定。
我看了 C++ Primer 中的一个例子来解释 std::move
。示例如下:
int &&rr1 = 42;
int &&rr3 = std::move(rr1);
上面代码片段的解释中是这样写的:
Calling move tells the compiler that we have an lvalue that we want to treat as if it were an rvalue. It is essential to realize that the call to move promises that we do not intend to use
rr1
again except to assign to it or to destroy it. After a call to move, we cannot make any assumptions about the value of the moved-from object.
我的第一个问题是:我现在可以安全地写 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++ Primer 一书中的 mistake/bug。
这两种情况都是安全的,因为没有移动操作(移动构造或移动赋值)发生。例如,在 auto&& str2 = std::move(str);
中,std::move(str)
仅生成一个 xvalue(右值),然后将其绑定到引用 str2
.
In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.
另一方面,
std::string str = "abc";
auto str2 = std::move(str); // move construction happens
这是来自 cppreference.com 的示例:
Unless otherwise specified, all standard library objects that have been moved from are placed in a "valid but unspecified state", meaning the object's class invariants hold (so functions without preconditions, such as the assignment operator, can be safely used on the object after it was moved from):
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
std::move
本身除了添加一个右值引用外什么都不做,所以这两个例子都是安全的。
std::move
和通过右值引用访问都不会对核心语言中对象的有效性或状态产生任何特殊影响。
std::move
的目的只是用作参数,例如函数调用,作为对被调用者的承诺,你不会关心调用后对象的状态。
如果函数例如不修改对象的状态,那么在将对象的状态传递给带有 std::move
的函数之后使用对象的状态也是明确定义的。由于 std::move
在任何情况下都会调用自身,所以没有未定义的行为。
所以这本书是正确的,std::move
的使用通常应被视为不依赖调用后对象状态的承诺。只是有点不清楚,promise 只是一个契约,不是核心语言的一部分。它是针对被调用者的,而不是针对编译器的。
但特别是标准库通常依赖于此承诺。当调用代码的其余部分错误地依赖标准库调用后的状态时,这可能会导致未定义的行为。由于标准库的特殊地位,本例中的承诺也是针对编译器的。
合同的细节有所不同。例如 std::unique_ptr
在移动后做出强有力的保证。以下是明确定义的并且始终满足断言条件:
auto p = std::make_unique<int>(1);
auto q = std::move(p);
auto ptr = p.get();
assert(ptr == nullptr);
如果没有此类特定保证,移动操作可能会导致所传递对象处于任何有效状态,这只是一个标准约定。