【发布时间】:2014-01-02 15:46:00
【问题描述】:
对于类类型,可以分配给内置类型实际上不允许的临时对象。此外,默认生成的赋值运算符甚至会产生一个左值:
int() = int(); // illegal: "expression is not assignable"
struct B {};
B& b = B() = B(); // compiles OK: yields an lvalue! ... but is wrong! (see below)
对于最后一条语句,赋值运算符的结果实际上用于初始化一个非const 引用,该引用将在语句之后立即变得陈旧:引用不直接绑定到临时对象(它不能因为临时对象只能绑定到 const 或右值引用),但只能绑定到生命周期未延长的赋值结果。
另一个问题是,从赋值运算符返回的左值看起来好像不能移动,尽管它实际上是指一个临时值。如果有任何东西正在使用分配的结果来获取值,它将被复制而不是移动,尽管移动它是完全可行的。此时值得注意的是,问题是根据赋值运算符来描述的,因为该运算符通常可用于值类型并返回左值引用。任何返回对象引用的函数都存在同样的问题,即*this。
一个潜在的解决方法是重载赋值运算符(或其他返回对象引用的函数)以考虑对象的类型,例如:
class G {
public:
// other members
G& operator=(G) & { /*...*/ return *this; }
G operator=(G) && { /*...*/ return std::move(*this); }
};
C++11 提供了像上面那样重载赋值运算符的可能性,可以防止上面提到的微妙对象失效,同时允许将赋值结果移动到临时对象。这两个运算符的实现可能是相同的。尽管实现可能相当简单(本质上只是两个对象的swap()),但这仍然意味着需要额外的工作来提出问题:
返回对象引用的函数(例如赋值运算符)是否应该观察被赋值对象的右值性?
另一种选择(Simple 在评论中提到)是不要重载赋值运算符,而是用 & 明确限定它以将其使用限制为左值:
class GG {
public:
// other members
GG& operator=(GG) & { /*...*/ return *this; }
};
GG g;
g = GG(); // OK
GG() = GG(); // ERROR
【问题讨论】:
-
我同意
&ref-qualifier 但我不喜欢&&过载。稍微相关的是,我的一个见解是,带有 ref 限定符的成员函数与非成员函数具有相同的“语义”,因为foo(T())和T().foo()都被禁止,而没有第一个不允许,但允许第二个。 -
@Dietmar:如果正确答案是“是”,那么委员会可能会进行一场尴尬的对话,他们决定是否修复编译器生成的复制/移动分配?所以这个问题可能有两个层面:(1)运营商是否应该在理想世界中这样做,以及(2)是否应该改变标准运营商来做到这一点。
-
@Simple: 是的,我同意解决该问题的另一种选择不是重载赋值运算符(或类似函数),而是明确限定它仅采用左值(我的意思是在问题但忘记了)。
-
(从已删除的评论中重新发布)我认为问题不仅限于分配。 任何返回左值引用的成员函数都可能导致悬空引用,例如
auto& x = std::vector<int>{1,2,3}.front();。 -
值得注意的是,您仍然可以将
= default与 ref 限定符一起使用,例如widget& operator=(widget const&) & = default。
标签: c++ c++11 rvalue-reference