1. 核心目的
是为了实现 “连锁赋值”(Chained Assignment)。
在 C++ 中,对于内置类型(如 int),我们习惯这样写代码:
int x, y, z;
x = y = z = 15; // 连锁赋值
由于赋值运算符是右结合(right-associative)的,上面的代码解析为: x = (y = (z = 15)); 为了让自定义的类也能支持这种写法,你的 operator= 必须返回一个指向对象本身的引用。
2. 标准写法示例
这是一个“协议”(Convention),虽然不是强制的语法要求,但所有的标准库类型(如 std::string, std::vector)都遵守这个协议。
class Widget {
public:
// 1. 返回类型必须是当前类的引用 (Widget&)
Widget& operator=(const Widget& rhs) {
// ...这里执行赋值操作(如拷贝数据)...
// 2. 返回 *this (即当前对象的引用)
return *this;
}
};
3. 使用场景演示
如果你遵守了 Item 10,用户就可以这样写:
Widget w1, w2, w3;
// ... 初始化 w3 ...
w1 = w2 = w3;
// 过程:
// 1. w2.operator=(w3) 被调用,返回 w2 的引用。
// 2. w1.operator=(w2) 被调用(参数是上一步返回的 w2),返回 w1 的引用。
4. 扩展适用范围
这个规则不仅适用于标准的赋值运算符 =,也适用于所有赋值相关的运算(compound assignment operators):
-
+= -
-= -
*= -
/=
class Widget {
public:
Widget& operator+=(const Widget& rhs) {
// ... 执行加法 ...
return *this; // 依然要返回 *this
}
};
5. 如果不遵守会怎样?
-
如果返回
void:代码编译可能没问题,但用户无法使用a = b = c这样的写法,这会让你的类显得“格格不入”,不符合 C++ 直觉。 -
如果返回对象(By Value):虽然支持连锁赋值,但会导致不必要的拷贝构造(Copy Constructor)调用,严重影响性能。
-
如果返回
const reference:虽然避免了拷贝,但会阻止某些合法的(虽然少见)操作,例如(a = b).func()可能会因为 const 限制而失败。
总结
这就是一个单纯的“从众”原则:既然 int 这样做,既然标准库这样做,为了让你的类好用且符合直觉,你也应该这样做。
记住模版:
ClassName& operator=(const ClassName& rhs) {
// ...
return *this;
}
2.如果返回 `const reference‘的问题
1. 实际代码限制:链式调用与立即修改
假设我们有一个类 Widget,并且它的赋值操作符返回 const Widget&:
C++
class Widget { public: // 【错误示范】返回 const 引用 const Widget& operator=(const Widget& rhs) { // ... 赋值逻辑 ... return *this; } // 一个普通的非 const 成员函数 void setup() { // 做一些初始化工作 } };
场景一:赋值后立即调用方法 (Method Chaining)
这是一种比较常见的写法,尤其是在需要对赋值后的对象立即进行某些操作时。
C++
Widget w1, w2; // 意图:把 w2 赋给 w1,然后立即初始化 w1 (w1 = w2).setup(); // ❌ 编译报错! // 原因: // 1. (w1 = w2) 返回的是 const Widget&(只读引用)。 // 2. setup() 是一个非 const 函数。 // 3. C++ 禁止在 const 对象上调用非 const 函数。
如果 operator= 返回的是普通的 Widget&,这行代码是完全合法的。
场景二:作为非 const 函数的参数
假设有一个函数需要修改传入的对象:
C++
void logAndModify(Widget& w); // 接受非 const 引用 Widget a, b; // 意图:把 b 赋给 a,然后把 a 传进去处理 logAndModify(a = b); // ❌ 编译报错! // 原因:logAndModify 需要 Widget&,但赋值表达式提供的是 const Widget&。
2. 与内置类型 (int) 的行为对比
这是 Scott Meyers 在《Effective C++》中反复强调的原则:“Do as the ints do”(像 int 那样行事)。
让我们看看 int 是怎么处理这种“奇怪”操作的:
C++
int x, y, z; x = 0; y = 1; z = 2; // 写法:(x = y) = z; // 意思是:先把 y 赋给 x,然后把 z 赋给 x。 // 最终结果:x 等于 2,y 等于 1,z 等于 2。 (x = y) = z;
这段代码在 C++ 中是合法的(虽然逻辑上有点奇怪,但在某些宏定义或特定的算法模板中可能会出现)。
-
x = y返回的是x的引用(可修改的左值)。 -
然后
x被再次赋值为z。
如果你的类返回 const reference:
C++
Widget w1, w2, w3; (w1 = w2) = w3; // ❌ 编译报错! // 原因:(w1 = w2) 返回的是 const 引用,你不能给一个 const 对象赋值。
总结
虽然 (a = b) = c 这种写法在实际业务代码中写出来可能会被同事打,但 (a = b).func() 这种写法是有实际应用场景的。
为了保证你的类:
-
灵活:支持连式调用和后续修改。
-
一致:表现得像内置类型,不让使用者感到意外(Principle of Least Astonishment)。
标准做法始终是: