c++ - 为什么重载的移动赋值运算符返回左值引用而不是右值引用?
问题描述
所以这真的只是我无法从语义上理解的东西。赋值链对复制语义有意义:
int a, b, c{100};
a = b = c;
a
, b
,c
都是100
.
用移动语义尝试类似的事情?只是不起作用或没有意义:
std::unique_ptr<int> a, b, c;
c = std::make_unique<int>(100);
a = b = std::move(c);
这甚至不能编译,因为a = b
是一个复制分配,它被删除了。我可以提出一个论点,即在最终表达式执行后,*a == 100
andb == nullptr
和c == nullptr
. 但这不是标准所保证的。这并没有太大变化:
a = std::move(b) = std::move(c);
这仍然涉及复制分配。
a = std::move(b = std::move(c));
这确实有效,但从语法上讲,它与复制分配链有很大的不同。
声明重载的移动赋值运算符涉及返回左值引用:
class MyMovable
{
public:
MyMovable& operator=(MyMovable&&) { return *this; }
};
但为什么它不是右值引用?
class MyMovable
{
public:
MyMovable() = default;
MyMovable(int value) { value_ = value; }
MyMovable(MyMovable const&) = delete;
MyMovable& operator=(MyMovable const&) = delete;
MyMovable&& operator=(MyMovable&& other) {
value_ = other.value_;
other.value_ = 0;
return std::move(*this);
}
int operator*() const { return value_; }
int value_{};
};
int main()
{
MyMovable a, b, c{100};
a = b = std::move(c);
std::cout << "a=" << *a << " b=" << *b << " c=" << *c << '\n';
}
有趣的是,这个例子实际上像我预期的那样工作,并给了我这个输出:
a=100 b=0 c=0
我不确定它是否不应该工作,或者为什么会这样,特别是因为正式的移动分配不是这样定义的。坦率地说,这只会在已经令人困惑的类类型语义行为世界中增加更多的混乱。
所以我在这里提出了一些事情,所以我将尝试将其浓缩为一组问题:
- 两种形式的赋值运算符都有效吗?
- 您什么时候使用其中一种?
- 移动分配链接是一回事吗?如果是这样,您何时/为什么会使用它?
- 您甚至如何链接以有意义的方式返回左值引用的移动赋值?
解决方案
是的,您可以自由地从重载的赋值运算符中返回您想要的任何内容,所以您MyMovable
很好,但是您的代码的用户可能会被意外的语义混淆。
我看不出有任何理由在移动分配中返回右值引用。移动分配通常如下所示:
b = std::move(a);
之后,b
应该包含 的状态a
,而a
将处于某个空或未指定的状态。
如果以这种方式链接它:
c = b = std::move(a);
那么你会期望b
不会失去它的状态,因为你从未申请std::move
过它。但是,如果您的移动赋值运算符通过右值引用返回,那么这实际上会将的状态移动a
到b
,然后左侧的赋值运算符也会调用移动赋值,将b
(a
之前的)的状态转移到c
。这是令人惊讶的,因为现在两者都a
具有b
空/未指定状态。相反,我希望a
被移动b
并复制到c
中,这正是移动赋值返回左值引用时会发生的情况。
现在为
c = std::move(b = std::move(a));
它按您的预期工作,在两种情况下都调用移动分配,很明显状态也b
被移动了。但是你为什么要这样做呢?您可以直接将a
' 的状态转移到with ,而无需在此过程中清除' 的状态(或者更糟糕的是,将其置于不可直接使用的状态)。即使你想要那样,它会更清楚地表述为一个序列c
c = std::move(a);
b
c = std::move(a);
b.clear(); // or something similar
至于
c = std::move(b) = std::move(a)
起码很明显是b
被移了,但是好像现在的状态是b
被移了,而不是右移之后的那个,双移是多余的。正如您所注意到的,这仍然调用左侧的复制分配,但如果您真的想在这两种情况下都调用移动分配,您可以通过右值引用返回并避免上述问题c = b = std::move(a)
,您会需要在中间表达式的值类别上进行区分。这可以通过以下方式完成:
MyMovable& operator=(MyMovable&& other) & {
...
return *this;
}
MyMovable&& operator=(MyMovable&& other) && {
return std::move(operator=(std::move(other)));
}
&
and限定符表示如果调用成员函数的表达式是左值或右值,&&
则应使用特定的重载。
我不知道您是否真的想这样做。
推荐阅读
- sql - 合并 1 行中的数据
- python - KeyError:使用箱线图时出现“final_result”
- azure - 用于组织、团队和用户管理的 Azure AD B2C 或 B2B
- unit-testing - 模拟 Kotlin 类不可模拟
- javascript - webpack模块测试正则表达式不起作用
- android - Android - NestedScrollView 不滚动
- ruby-on-rails - 更新对象时将对象 ID 传递给 Rails 邮件程序视图
- string - Android:将 URI 从一个活动发送到另一个活动,然后在 ImageView 中显示
- haskell - 类型类实例内的类型类约束
- javascript - 如何使用 Javascript Underscore.js 的 _.sortBy() 方法通过 javascript 中的多个键对对象数组进行排序?