以复合赋值运算符(op=)优化单独运算符(op)的实现
在 C++ 中,我们习惯了内置类型的便捷操作:比如 x = x + y
可以简写为 x += y
,两者天然兼容。但当我们定义自定义类型(如 Rational
有理数类)时,这种兼容性不会自动成立——C++ 不会默认关联 operator+
和 operator+=
。如何让自定义类型也具备这种“自然关联”,同时兼顾效率和代码复用?条款22给出了关键思路:以复合赋值运算符(如 +=
)为基础实现单独运算符(如 +
)。
一、问题:自定义类型的运算符“断档”
对于内置类型,x + y
和 x += y
是天然关联的,但自定义类型的运算符需要手动实现。如果我们希望:
Rational a, b;
a = a + b; // 单独运算符 operator+
a += b; // 复合赋值运算符 operator+=
同时成立,必须手动建立两者的关联——C++ 不会自动帮我们完成这件事。更重要的是,复合赋值运算符的效率通常更高,我们可以借助它优化单独运算符的实现。
二、复合赋值运算符的天然优势
复合赋值运算符(如 +=
)的核心特点:
- 直接修改左操作数:无需创建临时对象存储结果,减少构造/析构开销。
- 返回引用:支持链式调用(如
a += b += c
),同时符合语义预期。
以 Rational
类为例,复合赋值的实现通常是类的成员函数:
class Rational {
public:Rational& operator+=(const Rational& rhs) {// 直接修改当前对象的状态(如分子分母运算)return *this; }
};
三、以复合赋值实现单独运算符:复用的艺术
单独运算符(如 +
)需要返回新对象(因为 a + b
不能修改 a
或 b
),但我们可以复用复合赋值的逻辑,避免代码重复:
// 单独运算符 operator+ 作为非成员函数实现(保持对称性,避免友元,见条款19)
const Rational operator+(const Rational& lhs, const Rational& rhs) {// 1. 复制 lhs 到临时对象(构造一次)// 2. 调用复合赋值 +=,复用其逻辑// 3. 返回临时对象(利用返回值优化 RVO,减少析构开销)return Rational(lhs) += rhs;
}
关键设计细节:
- 单独运算符是“只读”的:参数用
const
,返回const
对象(避免(a + b) = c
这样的非法操作)。 - 复用复合赋值的逻辑:只需实现
+=
,+
就可以直接调用它,减少代码冗余。 - 非成员函数:单独运算符通常作为非成员函数,保证左、右操作数的对称性(比如支持
2 + a
,其中2
是内置类型,a
是Rational
),同时避免将+
设为友元(因为它调用的是公共的+=
)。
四、模板化:让复用更通用
如果多个类型(如 Rational
、Complex
等)都需要支持 +
,可以通过模板进一步复用逻辑:
template <typename T>
const T operator+(const T& lhs, const T& rhs) {return T(lhs) += rhs; // 只要 T 定义了 operator+=,即可复用
}
这样,只要自定义类型实现了 +=
,就能自动获得 +
的实现,彻底消除重复代码。
五、效率:复合赋值 vs 单独运算符
1. 复合赋值的效率优势
复合赋值(如 +=
)直接修改左操作数,无需创建临时对象,因此通常比单独运算符(如 +
)更高效。
2. 单独运算符的“临时对象”开销
单独运算符必须返回新对象,因此至少会产生一个临时对象(如 operator+
中 Rational(lhs)
的构造)。但通过复用 +=
,我们可以:
- 利用 返回值优化(RVO):编译器可将
return T(lhs) += rhs
优化为直接构造返回对象,减少一次拷贝。 - 对比“命名对象”的实现:如果手动创建命名对象(如
T result(lhs); result += rhs; return result;
),RVO 的优化空间会更小(命名对象更难被编译器优化)。因此,匿名临时对象的写法更利于编译器优化。
六、设计建议:给用户选择的权利
作为库设计者,应同时提供 复合赋值(+=
)和单独运算符(+
):
- 复合赋值:供性能敏感场景使用(如循环内的累加,避免临时对象)。
- 单独运算符:供表达式编写使用(如
a + b + c
,代码更简洁)。
作为应用开发者,性能敏感时优先选择复合赋值(如循环中用 a += b
而非 a = a + b
);代码简洁优先时,使用单独运算符。
七、总结:复用与效率的双赢
条款22的核心思想是 “以复合赋值为基础实现单独运算符”,带来三大收益:
- 代码复用:只需实现
+=
,即可推导+
,避免重复逻辑。 - 效率优化:复合赋值无临时对象开销;单独运算符通过 RVO 最大化优化。
- 行为一致:保证
a + b
和a += b
的逻辑一致,减少维护成本。
无论是自定义类型设计,还是通用模板库开发,这一思路都能帮我们写出更高效、更易维护的代码。记住:复合赋值是基石,单独运算符是延伸。