C++ 中面向对象的接口设计杂谈
原地修改?返回新对象?
我们来学点语法,关于 C++ 中类的 API 设计。
class Image {
public:// 原地修改版本Image& rotate(double angle) {// 实现...return *this;}// 返回新对象版本Image rotated(double angle) const {Image copy = *this;copy.rotate(angle); // 重用原地修改的实现return copy;}
};
有时需要维护一个对象的操作,这个操作的自修改写法比较简单,但是我们难免可能用到需要原对象不变而返回新对象的操作,我们可以像上面 rotated 函数一样写三行,复制-修改-返回(你最好别写别的;如果对象很大的话,这是最优解)。对应的 rotate 函数可以 return *this; 方便进行链式调用。
如果是反过来,自修改比较难绷,返回新对象的写法比较简单,那就写成静态成员函数或外部友元函数(如果全是 public 成员,写外部函数也行)。对应的自修改版本则直接 *this = func(*this);。例如 operator+ 比较简单,你就写这个,然后需要用到 operator+= 的时候,就 return *this = *this + rhs;。
引用延长生存期的规则
另外有一个难绷的误区,const auto& 和 auto&& 两个引用延长生存期,是当把临时对象绑定到它们时延长生存期,不是把引用绑定到它们,这可能导致悬垂引用。典型的误区形如用 const auto& 或 auto&& 接受一个返回 auto& 的函数的返回值,这是可能错误的,如果这个引用是临时对象的(典型如临时对象的 return *this),那么就会爆炸,直接用 auto 接受就可以了。但也不是说它们就不能接受引用了,只要你能保证引用所指的位置不会被提前析构掉就能接受。即要么绑定临时对象,要么绑定到生存期更长的对象的引用,否则将悬垂。注意绑定到临时对象的一部分也是悬垂,这和绑定临时对象引用是一个原理。
一个难绷的例子是 std::minmax - cppreference.cn - C++参考手册。如果 minmax 其中一个参数是临时对象,则返回的引用在包含对 minmax 调用的完整表达式结束时会变成悬空引用。这个例子套了一层 pair,问题会比较隐蔽。
int n = 1;
auto p = std::minmax(n, n + 1);
int m = p.first; // ok
int x = p.second; // undefined behavior
// Note that structured bindings have the same issue
auto [mm, xx] = std::minmax(n, n + 1);
xx; // undefined behavior
那当然 std::min - cppreference.cn - C++参考手册也一样有问题。如果其中一个参数是临时变量并且返回该参数,则通过引用捕获 std::min 的结果也会产生悬空引用。
int n = -1;
const int& r = std::min(n + 2, n * 2); // r is dangling