为什么 operator = return *this?
Why does operator = return *this?
说我想覆盖 operator =
这样我就可以做类似
的事情
Poly p1; // an object representing a polynomial
Poly p2; // another object of the same type
p2 = p1; // assigns all the contents of p1 to p2
然后在我的 operator =
实现中,我有这样的东西:
Poly& Poly::operator=(const Poly &source) {
// Skipping implementation, it already works fine…
return *this;
}
不要介意实现,它已经运行良好。
我担心的是 return *this
时会发生什么?我知道它 returns 对对象的引用,但这是发生了什么吗?
p2 = &p1
你 return *this
这样你就可以编写普通的复合 C++ =
语句,例如:
Poly p1; //an object representing a polynomial
Poly p2;
Poly p2;
// ...
p3 = p2 = p1; //assigns all the contents of p1 to p2 and then to p3
因为该语句基本上是:
p3.operator=(p2.operator=(p1));
如果 p2.operator=(...)
没有 return *this
你将没有任何有意义的东西可以传递给 p3.operator=(...)
。
p2 = p1
是 p2.operator=(p1)
的 shorthand。它只是调用您的 operator=
函数,该函数返回对 p2
的引用,然后您将忽略它。为了清楚起见,我们将其称为 assign
而不是 operator=
:
Poly& Poly::assign(const Poly &source) {
.
.
.
return *this;
}
现在 p2 = p1
你会写
p2.assign(p1);
在这种情况下,调用 assign
的结果将被忽略,但您不必忽略它。例如,您可以这样写:
p3.assign(p2.assign(p1));
使用 operator=
而不是 assign
,这就变成了
p3 = (p2 = p1);
但由于赋值是right-associative,这也可以写成
p3 = p2 = p1;
这种能够同时进行多项赋值的形式最初来自 C,并通过在 operator=()
中返回 *this
的约定保留在 C++ 中。
返回对目标对象的引用允许赋值链接(级联),class 中的重载运算符遵循 right-associative(单击 here 了解详细的运算符重载规则)
Poly a, b, c;
a = b = c;
如果您无论如何都不需要链式赋值(如其他答案所示),则可能会想使 copy-assignment 运算符 return void
。毕竟,链式赋值通常难以阅读和理解,因此不允许它们可能被视为 改进。
然而,一个经常被忽视的方面是 void operator=(Poly& const)
意味着您的类型将不再满足 CopyAssignable
concept,这需要 T&
return 类型。
不满足 CopyAssignable
概念的类型不能正式用于某些 standard-container 操作,例如 std::vector::insert
,这意味着以下看似无辜的代码产生未定义的行为,即使它可能 运行 完全没问题:
#include <vector>
struct Poly
{
void operator=(Poly const&) {} // Poly is not CopyAssignable
};
int main()
{
std::vector<Poly> v;
Poly p;
v.insert(v.begin(), p); // undefined behaviour
}
正如 C++ 标准在 § 17.6.4.8/2.3 中解释的那样,它讨论了对使用标准库的程序的约束:
(...) the effects are undefined in the following cases:
(...) for types used as template arguments when instantiating a
template component, if the operations on the type do not implement the
semantics of the applicable Requirements subclause (...).
当然,恰恰是因为未定义的行为允许编译器忽略错误并使程序表现得很好,与明显预期的行为相匹配。但这不是必需的。
您还应该考虑到您无法预测 Poly
类型的所有未来用途。有人可能会写一些模板函数,例如:
template <class T>
void f(T const& t)
{
T t2;
T t3 = t2 = t;
// ...
}
此功能将不适用于您的 Poly
class。
只要不违反此 C++ 约定,您就不会 运行 惹上麻烦。
what happens when you return *this?
在您的示例 (p2 = p1;
) 中,什么也没有。该方法将 p1
复制到 p2
和 returns 对 'this' 对象的引用,调用代码不使用该对象。
在 p3 = p2 = p1;
等代码中,第一个调用是 p2 = p1
,它将 p1
复制到 p2
和 returns 对 [=12] 的引用=].然后调用代码从 reference-to-p2
复制到 p3
(并忽略返回的对 p3
的引用)。
(顺便说一下:您的单元测试是否确保 p1 = p1
正常工作?很容易忘记这种情况!)
说我想覆盖 operator =
这样我就可以做类似
Poly p1; // an object representing a polynomial
Poly p2; // another object of the same type
p2 = p1; // assigns all the contents of p1 to p2
然后在我的 operator =
实现中,我有这样的东西:
Poly& Poly::operator=(const Poly &source) {
// Skipping implementation, it already works fine…
return *this;
}
不要介意实现,它已经运行良好。
我担心的是 return *this
时会发生什么?我知道它 returns 对对象的引用,但这是发生了什么吗?
p2 = &p1
你 return *this
这样你就可以编写普通的复合 C++ =
语句,例如:
Poly p1; //an object representing a polynomial
Poly p2;
Poly p2;
// ...
p3 = p2 = p1; //assigns all the contents of p1 to p2 and then to p3
因为该语句基本上是:
p3.operator=(p2.operator=(p1));
如果 p2.operator=(...)
没有 return *this
你将没有任何有意义的东西可以传递给 p3.operator=(...)
。
p2 = p1
是 p2.operator=(p1)
的 shorthand。它只是调用您的 operator=
函数,该函数返回对 p2
的引用,然后您将忽略它。为了清楚起见,我们将其称为 assign
而不是 operator=
:
Poly& Poly::assign(const Poly &source) {
.
.
.
return *this;
}
现在 p2 = p1
你会写
p2.assign(p1);
在这种情况下,调用 assign
的结果将被忽略,但您不必忽略它。例如,您可以这样写:
p3.assign(p2.assign(p1));
使用 operator=
而不是 assign
,这就变成了
p3 = (p2 = p1);
但由于赋值是right-associative,这也可以写成
p3 = p2 = p1;
这种能够同时进行多项赋值的形式最初来自 C,并通过在 operator=()
中返回 *this
的约定保留在 C++ 中。
返回对目标对象的引用允许赋值链接(级联),class 中的重载运算符遵循 right-associative(单击 here 了解详细的运算符重载规则)
Poly a, b, c;
a = b = c;
如果您无论如何都不需要链式赋值(如其他答案所示),则可能会想使 copy-assignment 运算符 return void
。毕竟,链式赋值通常难以阅读和理解,因此不允许它们可能被视为 改进。
然而,一个经常被忽视的方面是 void operator=(Poly& const)
意味着您的类型将不再满足 CopyAssignable
concept,这需要 T&
return 类型。
不满足 CopyAssignable
概念的类型不能正式用于某些 standard-container 操作,例如 std::vector::insert
,这意味着以下看似无辜的代码产生未定义的行为,即使它可能 运行 完全没问题:
#include <vector>
struct Poly
{
void operator=(Poly const&) {} // Poly is not CopyAssignable
};
int main()
{
std::vector<Poly> v;
Poly p;
v.insert(v.begin(), p); // undefined behaviour
}
正如 C++ 标准在 § 17.6.4.8/2.3 中解释的那样,它讨论了对使用标准库的程序的约束:
(...) the effects are undefined in the following cases:
(...) for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause (...).
当然,恰恰是因为未定义的行为允许编译器忽略错误并使程序表现得很好,与明显预期的行为相匹配。但这不是必需的。
您还应该考虑到您无法预测 Poly
类型的所有未来用途。有人可能会写一些模板函数,例如:
template <class T>
void f(T const& t)
{
T t2;
T t3 = t2 = t;
// ...
}
此功能将不适用于您的 Poly
class。
只要不违反此 C++ 约定,您就不会 运行 惹上麻烦。
what happens when you return *this?
在您的示例 (p2 = p1;
) 中,什么也没有。该方法将 p1
复制到 p2
和 returns 对 'this' 对象的引用,调用代码不使用该对象。
在 p3 = p2 = p1;
等代码中,第一个调用是 p2 = p1
,它将 p1
复制到 p2
和 returns 对 [=12] 的引用=].然后调用代码从 reference-to-p2
复制到 p3
(并忽略返回的对 p3
的引用)。
(顺便说一下:您的单元测试是否确保 p1 = p1
正常工作?很容易忘记这种情况!)