25 October 2022

C++ 核心指南目录

C.60: Make copy assignment non-virtual, take the parameter by const&, and return by non-const&

理由

如此操作,简单高效。如想针对右值进行优化,可提供一个重载的函数,接受一个&&右值引用作为参数。

例子

class Foo {
public:
    Foo& operator=(const Foo& x)
    {
        // GOOD: no need to check for self-assignment (other than performance)
        auto tmp = x;
        swap(tmp); // see C.83
        return *this;
    }
    // ...
};

Foo a;
Foo b;
Foo f();

a = b;    // assign lvalue: copy
a = f();  // assign rvalue: potentially move

注意

swap 实现技术提供有效保障。

例子

但是,如果你想要避免临时副本,获取更好的性能。考虑一个简单的 Vector 情况,包含元素比较多,且大小一样的 Vector 之间赋值的情况就比较常见。这种情况,用 swap 实现就会导致比较大的性能开销。

template<typename T>
class Vector {
public:
    Vector& operator=(const Vector&);
    // ...
private:
    T* elem;
    int sz;
};

Vector& Vector::operator=(const Vector& a)
{
    if (a.sz > sz) {
        // ... use the swap technique, it can't be bettered ...
        return *this;
    }
    // ... copy sz elements from *a.elem to elem ...
    if (a.sz < sz) {
        // ... destroy the surplus elements in *this and adjust size ...
    }
    return *this;
}

通过直接改写目标元素,我们不能像 swap 技术那样实现完备的保证,只能达到基础保证。所以需要注意自己给自己赋值这种情况。

其他选项

如果你需要一个虚的赋值操作符,但是明白可能有深层次的问题,那可以不叫它为operator=,而取名为 virtual void assign(const Foo&)

强化

  • (简单)赋值操作符必须不能是 virtual 的。小心恶龙出没。
  • (简单)赋值操作符要返回T&,从而可以串接起来使用。不要用 const T& ,这样会影响代码编排,无法把对象放入容器。
  • (中等难度)赋值操作符应该(隐式或显示)地调用基类和成员的赋值操作符。查看析构函数确定类型是否有指针语义或值语义。