构造函数和复制控制成员不能继承(即构造函数,析构函数,复制构造函数,赋值操作符),每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。
² 派生类构造函数
派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。派生类的合成默认构造函数与非派生的构造函数只有一点不同:除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化。
向基类构造函数传递实参,派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。如:
Bulk_item(const std::string&book, double sales_price,std::size_t qty = 0, double disc_rate = 0.0):Item_base(book, sales_price),min_qty(qty),discount(disc_rate) { }
ps:构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序。首先初始化基类,然后根据变量声明次序初始化派生类的成员。
pps:派生类构造函数只能初始化自己的直接基类,在 派生类的构造函数初始化列表中指定间接基类是一个错误。
² 复制控制
类是否需要定义复制控制成员完全取决于类自身的直接成员。基类可以定义自己的复制控制而派生类使用合成版本,反之亦然。Item_base类及其派生类可以使用复制控制操作的合成版本。复制Bulk_item 对象时, 调用 (合成的) Item_base 复制构造函数复制 isbn 和 price
成员。使用 string 复制构造函数复制 isbn,直接复制 price 成员。一旦复制了基类部分,再复制派生部分。Bulk_item 的两个成员都是 double 型,直接复制这些成员。赋值操作符和析构函数类似处理。
² 定义派生类的复制构造函数
如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分:
class Base{ /* ... */ };
classDerived: public Base {
public:
// Base::Base(constBase&) not invoked automatically
Derived(const Derived& d):Base(d) /* other member initialization */ { /*... */ }
};
初始化函数 Base(d) 将派生类对象 d 转换为它的基类部分的引用,并调用基类复制构造函数。
² 派生类赋值操作符
赋值操作符通常与复制构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。
// Base::operator=(const Base&) not invoked automatically
Derived& Derived::operator=(const Derived &rhs)
{
if (this != &rhs) { //防止自身赋值!
Base::operator=(rhs); //assigns the base part
// assign the membersfrom the derived
}
return *this;
}
² 派生类析构函数
析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员:
classDerived: public Base {
public:
// Base::~Baseinvoked automatically,不同与上面
~Derived() { /* do whatit takes to clean up derived members*/ }
};
对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。
² 虚析构函数
删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数:
classItem_base {
public:
virtual ~Item_base() { }
};
Item_base*itemP = new Item_base;
deleteitemP; // ok: destructor forItem_base called
itemP =new Bulk_item;
deleteitemP; // ok: destructor forBulk_item called
像其他虚函数一样,析构函数的虚函数性质都将继承。因此,如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。即使析构函数没有工作要做,继承层次的根类也应该定义一个空的虚析构函数。
*****构造函数和赋值操作符不是虚函数*******
在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。
将赋值操作符设为虚函数可能会令人混淆,因为虚函数必须在基类和派生类中具有同样的形参(即基类对象的引用)。但是,对派生类而言,这个操作符与赋值操作符是不同的。