x = std::move(x) 是否未定义?
Is x = std::move(x) undefined?
让x
成为一个先前已经初始化的某种类型的变量。是下面一行:
x = std::move(x)
未定义?这在标准中的什么位置?它是怎么说的?
它将调用 X::operator = (X&&)
,因此由实施来管理这种情况(就像 X::operator = (const X&)
所做的那样)
不,这不是未定义的行为,它将是实现定义的行为,这将取决于移动分配的实现方式。
与此相关的是 LWG issue 2468: Self-move-assignment of library types ,请注意这是一个活跃的问题并且没有官方提案,因此这应该被认为是指示性的而不是确定性的,但它确实指出了涉及的部分标准库并指出它们目前存在冲突。它说:
Suppose we write
vector<string> v{"a", "b", "c", "d"};
v = move(v);
What should be the state of v be? The standard doesn't say anything
specific about self-move-assignment. There's relevant text in several
parts of the standard, and it's not clear how to reconcile them.
[...]
It's not clear from the text how to put these pieces together, because it's not clear which one takes precedence. Maybe 17.6.4.9 [res.on.arguments] wins (it imposes an implicit precondition that isn't mentioned in the MoveAssignable requirements, so v = move(v) is undefined), or maybe 23.2.1 [container.requirements.general] wins (it explicitly gives additional guarantees for Container::operator= beyond what's guaranteed for library functions in general, so v = move(v) is a no-op), or maybe something else.
On the existing implementations that I checked, for what it's worth, v = move(v) appeared to clear the vector; it didn't leave the vector unchanged and it didn't cause a crash.
并提议:
Informally: change the MoveAssignable and Container requirements tables (and any other requirements tables that mention move assignment, if any) to make it explicit that x = move(x) is defined behavior and it leaves x in a valid but unspecified state. That's probably not what the standard says today, but it's probably what we intended and it's consistent with what we've told users and with what implementations actually do.
注意,对于内置类型,这基本上是一个副本,我们可以从草案 C++14 标准部分看到 5.17
[expr.ass]:
In simple assignment (=), the value of the expression replaces that of the object referred to by the left
operand.
这与 类 的情况不同,其中 5.17
表示:
If the left operand is of class type, the class shall be complete. Assignment to objects of a class is defined
by the copy/move assignment operator (12.8, 13.5.3).
注意,clang 有一个 self move warning:
Log:
Add a new warning, -Wself-move, to Clang.
-Wself-move is similiar to -Wself-assign. This warning is triggered when
a value is attempted to be moved to itself. See r221008 for a bug that
would have been caught with this warning.
所做的就是调用 X::operator=(X&&)
(左值限定为“*this
”)。
在原始类型上,std::move
没有什么用处,并且根本不与 =
交互。所以这只适用于 class 类型的对象。
现在,对于 std
中的类型(或由其模板之一生成),来自 move
d 的对象往往处于未指定(但有效)状态。这不是未定义的行为,但不是有用的行为。
必须检查每个给定 X::operator=(X&&)
的语义,检查 std
中的每个类型对于堆栈溢出答案将是 "too broad"。他们甚至会自相矛盾。
一般来说,当 move
从对象中获取时,您是在与消费者沟通 "you don't care what state the object is in afterwards"。因此,使用 x = std::move(x)
是不礼貌的,因为您(通常) do 关心操作完成后 x
处于什么状态(因为您正在分配给它).您在同一个操作中使用同一个对象作为左值和右值,这不是好的做法。
一个有趣的例外是默认值 std::swap
,它是这样的:
template<class T>
void swap(T& lhs, T& rhs) {
T tmp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(tmp);
}
中间行 lhs = std::move(rhs)
如果您对同一对象调用两次交换,则执行 x = std::move(x)
。
但是请注意,我们不关心x
完成此行后处于什么状态;我们已经在tmp
中存储了x
的状态,我们将在下一行恢复它。
让x
成为一个先前已经初始化的某种类型的变量。是下面一行:
x = std::move(x)
未定义?这在标准中的什么位置?它是怎么说的?
它将调用 X::operator = (X&&)
,因此由实施来管理这种情况(就像 X::operator = (const X&)
所做的那样)
不,这不是未定义的行为,它将是实现定义的行为,这将取决于移动分配的实现方式。
与此相关的是 LWG issue 2468: Self-move-assignment of library types ,请注意这是一个活跃的问题并且没有官方提案,因此这应该被认为是指示性的而不是确定性的,但它确实指出了涉及的部分标准库并指出它们目前存在冲突。它说:
Suppose we write
vector<string> v{"a", "b", "c", "d"}; v = move(v);
What should be the state of v be? The standard doesn't say anything specific about self-move-assignment. There's relevant text in several parts of the standard, and it's not clear how to reconcile them.
[...]
It's not clear from the text how to put these pieces together, because it's not clear which one takes precedence. Maybe 17.6.4.9 [res.on.arguments] wins (it imposes an implicit precondition that isn't mentioned in the MoveAssignable requirements, so v = move(v) is undefined), or maybe 23.2.1 [container.requirements.general] wins (it explicitly gives additional guarantees for Container::operator= beyond what's guaranteed for library functions in general, so v = move(v) is a no-op), or maybe something else.
On the existing implementations that I checked, for what it's worth, v = move(v) appeared to clear the vector; it didn't leave the vector unchanged and it didn't cause a crash.
并提议:
Informally: change the MoveAssignable and Container requirements tables (and any other requirements tables that mention move assignment, if any) to make it explicit that x = move(x) is defined behavior and it leaves x in a valid but unspecified state. That's probably not what the standard says today, but it's probably what we intended and it's consistent with what we've told users and with what implementations actually do.
注意,对于内置类型,这基本上是一个副本,我们可以从草案 C++14 标准部分看到 5.17
[expr.ass]:
In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand.
这与 类 的情况不同,其中 5.17
表示:
If the left operand is of class type, the class shall be complete. Assignment to objects of a class is defined by the copy/move assignment operator (12.8, 13.5.3).
注意,clang 有一个 self move warning:
Log: Add a new warning, -Wself-move, to Clang.
-Wself-move is similiar to -Wself-assign. This warning is triggered when a value is attempted to be moved to itself. See r221008 for a bug that would have been caught with this warning.
所做的就是调用 X::operator=(X&&)
(左值限定为“*this
”)。
在原始类型上,std::move
没有什么用处,并且根本不与 =
交互。所以这只适用于 class 类型的对象。
现在,对于 std
中的类型(或由其模板之一生成),来自 move
d 的对象往往处于未指定(但有效)状态。这不是未定义的行为,但不是有用的行为。
必须检查每个给定 X::operator=(X&&)
的语义,检查 std
中的每个类型对于堆栈溢出答案将是 "too broad"。他们甚至会自相矛盾。
一般来说,当 move
从对象中获取时,您是在与消费者沟通 "you don't care what state the object is in afterwards"。因此,使用 x = std::move(x)
是不礼貌的,因为您(通常) do 关心操作完成后 x
处于什么状态(因为您正在分配给它).您在同一个操作中使用同一个对象作为左值和右值,这不是好的做法。
一个有趣的例外是默认值 std::swap
,它是这样的:
template<class T>
void swap(T& lhs, T& rhs) {
T tmp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(tmp);
}
中间行 lhs = std::move(rhs)
如果您对同一对象调用两次交换,则执行 x = std::move(x)
。
但是请注意,我们不关心x
完成此行后处于什么状态;我们已经在tmp
中存储了x
的状态,我们将在下一行恢复它。