如何避免重载赋值运算符将右值转换为左值

How to avoid overloaded assignment operator turning rvalue to lvalue?

本文关键字:转换 何避免 重载 赋值运算符      更新时间:2023-10-16

据我所知,返回*this是编写重载operator=的规范。但这可能会将右值提升为左值!

struct Foo {
  Foo& operator=(const Foo &t) { return *this; }
};
int main() {
  const Foo &ref = Foo();
  //Foo &ref1 = Foo();  // This line won't compile.
  //But the following line compiles. Isn't it dangerous?
  //Also please note that if = is not overloaded, the synthesized operator= will NOT let it compile.
  Foo &ref2 = (Foo() = Foo());
  return 0;
}

你说得对。简单的回答是,这是合法的,但由于你指出的原因,这是危险的。

另一个(类似的)例子被添加到C++11中,作为标准的一部分,带有右值流输出,返回一个左值引用。

template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

因此std::ostream& s = std::ofstream("foo.txt") << "Oh boy";是合法的,但它创建了一个悬空引用。

我相信,C++11编译器将允许成员函数被限制在对象为左值的情况下,这应该可以让你完全避免这个问题。以下仅允许在*this为左值时进行赋值,因此可以防止意外转换。

struct Foo {
  Foo& operator=(const Foo &t) & { return *this; }
};

正如第一个答案所指出的,您所做的是合法的,但会导致悬空引用。您可以在不重载赋值运算符的情况下获得大致相同的结果:

#include <iostream>
struct Foo {
  Foo(int arg) : val(arg) { std::cout << "ctor " << val << "n"; }
  ~Foo() { std::cout << "dtor " << val << "n"; }
  int val;
};
int main() {
  Foo &ref = (Foo(1) = Foo(2));
  std::cout << "undefined behavior: " << ref.val << "n";
  return 0;
}

产生以下输出:

ctor 1
ctor 2
dtor 2
dtor 2
未定义行为:2

如您所见,先构造Foo(1)的临时,然后构造Foo(2)的临时。然而,在我们可以对引用执行任何操作之前,这两个对象都已被销毁,因此后面的行将导致未定义的行为。