高级程序设计

16 - 派生类的初始化和消亡处理

2020-03-26 15:00 CST
2020-03-27 22:10 CST
CC BY-NC 4.0

派生类对象的初始化

派生类对象的初始化由基类和派生类共同完成:

  • 从基类继承的数据成员有基类的构造函数初始化;
  • 派生类的数据成员由派生类的构造函数初始化。

默认情况下,当创建派生类的对象时,调用基类的默认构造函数。如果需要调用基类的非默认构造函数,则必须在派生类构造函数的成员初始化表中指出。如:

class A {
  private:
    int x;
  public:
    A() { x = 0; }
    A(int i) { x = i; }
};
class B : public A {
  public:
    B(int i) : A(i) { }
};

派生类对象的析构

派生类对象的析构由基类和派生类共同完成:

  • 从基类继承的数据成员由基类的析构函数析构;
  • 派生类的数据成员由派生类的析构函数析构。

派生类对象的初始化、析构顺序

当创建派生类的对象时:先执行基类的构造函数,再执行派生类构造函数。

当析构派生类的对象时:先执行派生类的析构函数,再执行基类析构函数。

如类D既有基类B,又有成员对象M,则

  • 构造函数的执行顺序:B -> M -> D
  • 析构函数的执行顺序:D -> M -> B

派生类拷贝构造函数

  • 派生类的隐式拷贝构造函数会调用基类的拷贝构造函数。
  • 派生类自定义的拷贝构造函数在默认情况下则调用基类的默认构造函数。需要时,可在派生类自定义拷贝构造函数的成员初始化表中显式地指出调用基类的拷贝构造函数。
class B : public A {
  public:
    B(const B& b) : A(b) {
      // .....
    }
};

派生类对象的赋值操作

  • 派生类隐式的赋值操作除了对派生类成员进行复制外,还将调用基类的赋值操作对基类成员进行赋值。
  • 派生类自定义的赋值操作符重载函数不会自动调用基类的赋值操作,需要在自定义的赋值操作符重载函数中显式地指出。
class B : public A {
  public:
    B &operator=(const B& b) {
      if (&b == this) return *this;
      // 调用基类的赋值操作符
      *(A*)this = b;
      // 或者 this->A::operator=(b);
      return *this;
    }
};

转移构造函数 & 转移赋值操作符重载

拷贝构造:在创建一个对象用另一个同类型的对象对其初始化时,会调用拷贝构造函数。

拷贝构造的问题:当用一个临时或者即将消亡的对象去初始化另一个同类的对象时,目前的拷贝构造函数的实现效率有时是不高的。(因为可能申请新的内存空间再把已有的释放掉,效率低下)

赋值操作符的问题:把一个对象赋值给另一个对象时,将会去调用对象类的赋值操作符重载函数。同样的,当用于赋值的对象是一个临时或即将消亡的对象时,目前的复制操作符函数的实现效率有时是不高的。

转移构造函数

C++11标准中提供了一种新的构造函数:转移构造函数(move constructor):A(A &&x);(参数类型为右值引用类型&&)。

当用一个临时对象或即将消亡的对象去初始化另一个对象时:

  • 如果对象类中有转移构造函数,则会去调用转移构造函数来进行初始化;
  • 否则去调用拷贝构造函数进行对象初始化。
  • 注意:编译器不会提供隐式转移构造函数。

在转移构造函数中实现资源的转移:

class A {
  private:
    char *p;
  public:
    A(A &&x) {
      p = x.p;    // 资源转移
      x.p = NULL; // 使对象p不再拥有原来所指向的空间
    }
}

转移赋值操作符重载函数

C++11标准中允许在类中定义一个转移赋值操作符重载函数(move assignment operator)A& operator=(A &&x);

当用于赋值的对象是一个临时的或即将消亡的对象时:

  • 如果对象类中有转移赋值操作符重载函数,则会去调用它来实现对象的赋值;
  • 否则将调用的普通的复制操作符重载函数来实现对象的赋值。
  • 注意:编译器不会提供隐式的转移赋值操作符重载函数。
class A {
  private:
    char *p;
  public:
    A &operator==(A &&x) {
      if (&this == this) return *this;
      delete []p; // 归还老空间
      p = x.p;    // 资源转移
      x.p = NULL; // 使对象p不再拥有原来所指向的空间
      return *this;
    }
}