复制构造和赋值运算符

vptr

这个小节没有提到 vptr 的处理。

  1. 对于复制构造函数,其本质还是构造,所以和一般的构造函数流程相似,会正确设置好 vptr。
  2. 对于赋值运算符,原有 vptr 和新的 vptr 是一样的,不需要修改。

怎么写复制构造/赋值?

写复制构造函数和复制赋值操作符的时候要记得把基类和成员都正确初始化(用默认的最好)。复制构造函数也算是构造函数,所以要使用成员初始化列表来正确初始化。

复制赋值操作符对基类赋值需要先将 *this 转换成基类的引用,然后用赋值表达式调用基类的赋值操作符。毕竟赋值操作符也不是构造函数,构造过程需要手写的部分有很多还容易写错!这也是为什么复制构造临时对象 + swap 的方法很受欢迎。

#include <cstdio>

struct A {
    int a;
    A() = default;
    A(A const &) { puts("A(const A &)"); }
    A &operator=(A const &) {
        puts("A &operator=(A const &)");
        return *this;
    }
};

struct B {
    int b;
    B() = default;
    B(B const &) { puts("B(const B &)"); }
    B &operator=(B const &) {
        puts("B &operator=(B const &)");
        return *this;
    }
};

struct D {
    int d;
    D() = default;
    D(D const &) { puts("D(const D &)"); }
    D &operator=(D const &) {
        puts("D &operator=(D const &)");
        return *this;
    }
};

struct C : A, B {
    int c;
    D d;
    C() = default;

    // 错误写法:(没有通过初始化列表对基类和成员起到复制作用,它们被默认构造)
    // C(const C &) {
    // }

    // 等效于默认的复制构造函数
    C(const C &other) : A(other), B(other), d{other.d} {}

    // 等效于默认的赋值操作符
    // 很麻烦,不如先复制构造再swap
    C &operator=(C const &other) {
        if (this == &other) {
            return *this;
        }
        // 右边的 other 会隐式转换成对应类型
        static_cast<A &>(*this) = other;
        static_cast<B &>(*this) = other;
        d = other.d;

        return *this;
    }
};

int main() {
    C c;
    puts("copy construction");
    C c1 = c;
    puts("copy assignment");
    c1 = c;
}

输出为:

copy construction
A(const A &)
B(const B &)
D(const D &)
copy assignment
A &operator=(A const &)
B &operator=(B const &)
D &operator=(D const &)