每个派生类对象由派生类中定义的(非static)成员加上一个或多个基类子对象构成,因此,当构造、复制、赋值和撤销派生类型对象时,也会构造、复制、赋值和撤销这些基类子对象。
构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。
1:构造函数和继承
派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。
派生类的合成默认构造函数:除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { cout << "father constructor" << endl; } virtual void display() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: void display() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]prot_i is " << prot_i << endl; } }; int main() { son ss1; ss1.display(); }
执行”son ss1;”语句时,调用派生类son的合成的默认构造函数,该函数会首先调用基类的默认构造函数。上述代码的结果是:
father constructor [son]publ_i is 1 [son]prot_i is 3
如果派生类自己定义了构造函数,则该构造函数会隐式调用基类的默认构造函数:
class son: public father { public: son(int a = 2):mypriv_i(a) { cout << "son constructor" << endl; } void display() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]prot_i is " << prot_i << endl; cout << "[son]mypriv_i is " << mypriv_i << endl; } private: int mypriv_i; }; int main() { son ss1; ss1.display(); }
执行”son ss1;”语句时,调用派生类son的默认构造函数,该函数首先调用基类的默认构造函数初始化基类部分,然后,使用初始化列表初始化son::mypriv_i,最后,在执行son构造函数的函数体。上述代码的结果是:
father constructor son constructor [son]publ_i is 1 [son]prot_i is 3 [son]mypriv_i is 2
派生类构造函数还可以明确的在初始化列表中调用基类的构造函数,来间接的初始化基类成员。注意,在派生类列表中,不能直接初始化基类成员:
class son: public father { public: son(int a = 2):mypriv_i(a),father(100) { cout << "son constructor" << endl; } void display() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]prot_i is " << prot_i << endl; cout << "[son]mypriv_i is " << mypriv_i << endl; } private: int mypriv_i; }; int main() { son ss1; ss1.display(); }
不管初始化列表中的顺序如何,派生类构造函数,都是首先调用基类构造函数初始化基类成员,然后是根据派生类成员的声明顺序进行初始化。
如果尝试在初始化列表中直接初始化基类成员,会发生编译错误:
son(int a = 2):mypriv_i(a),publ_i(100) { cout << "son constructor" << endl; }
报错如下:
test2.cpp: In constructor ‘son::son(int)’: test2.cpp:29:29: error: class ‘son’ does not have any field named ‘publ_i’ son(int a = 2):mypriv_i(a),publ_i(100) ^
派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为 public 或 protected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。
注意,一个类只能初始化自己的直接基类。
2:复制控制和继承
派生类合成的复制控制函数,对对象的基类部分连同派生部分的成员一起进行复制、赋值或撤销,自动调用基类的复制构造函数、赋值操作符或析构函数对基类部分进行复制、赋值或撤销:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { cout << "father constructor" << endl; } father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i) { cout << "father copy constructor" << endl; } father& operator=(const father& src) { publ_i = src.publ_i; priv_i = src.priv_i; prot_i = src.prot_i; cout << "father operator = " << endl; return *this; } ~father() { cout << "father destructor" << endl; } virtual void display() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: son(int a = 2):mypriv_i(a) { cout << "son constructor" << endl; } void display() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]prot_i is " << prot_i << endl; cout << "[son]mypriv_i is " << mypriv_i << endl; } private: int mypriv_i; }; int main() { son ss1(3); son ss2(ss1); ss2.display(); son ss3; ss3 = ss1; ss3.display(); }
“son ss2(ss1);”语句,将调用派生类son的合成的复制构造函数,该构造函数将会自动调用基类的复制构造函数;”ss3 = ss1;”语句,将调用派生类son的合成的赋值操作符函数,该函数会自动调用基类的赋值操作符函数;最后,程序退出之前,会将派生类son的三个对象进行析构,从而自动调用基类的析构函数;上述代码的结果如下:
father constructor son constructor father copy constructor [son]publ_i is 1 [son]prot_i is 3 [son]mypriv_i is 3 father constructor son constructor father operator = [son]publ_i is 1 [son]prot_i is 3 [son]mypriv_i is 3 father destructor father destructor father destructor
如果派生类自己定义了复制构造函数或赋值操作符,则应该显示的调用基类的复制构造函数或赋值操作符:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { cout << "father constructor" << endl; } father(const father &src):publ_i(src.publ_i), priv_i(src.priv_i), prot_i(src.prot_i) { cout << "father copy constructor" << endl; } father& operator=(const father& src) { publ_i = src.publ_i; priv_i = src.priv_i; prot_i = src.prot_i; cout << "father operator = " << endl; return *this; } ~father() { cout << "father destructor" << endl; } virtual void display() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: son(int a = 2):mypriv_i(a),father(100) { cout << "son constructor" << endl; } son(const son& src):mypriv_i(src.mypriv_i),father(src) { cout << "son copy constructor" << endl; } son& operator=(const son& src) { father::operator=(src); mypriv_i = src.mypriv_i; cout << "son operator = " << endl; return *this; } void display() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]prot_i is " << prot_i << endl; cout << "[son]mypriv_i is " << mypriv_i << endl; } private: int mypriv_i; }; int main() { son ss1(3); son ss2(ss1); ss2.display(); son ss3; ss3 = ss1; ss3.display(); }
在派生类son的复制构造函数中,初始化列表中显示调用基类复制构造函数使用src初始化基类部分。这里如果省略调用基类复制构造函数的调用的话,编译器会自动调用基类的默认构造函数初始化基类部分,这就造成新构造的对象会有比较奇怪的配置,它的基类部分保存默认值,而他的派生类部分是src的副本;
在派生类son的赋值操作符函数中,显示调用了基类的赋值操作符函数。如果省略了这一调用,则赋值操作,仅仅使该对象的mypriv_i与src的mypriv_i相同;
上述代码的结果如下:
father constructor son constructor father copy constructor son copy constructor [son]publ_i is 100 [son]prot_i is 3 [son]mypriv_i is 3 father constructor son constructor father operator = son operator = [son]publ_i is 100 [son]prot_i is 3 [son]mypriv_i is 3 father destructor father destructor father destructor
如果定义了派生类的析构函数,则不同于复制构造函数和赋值操作符:编译器会自动调用基类的析构函数,因此在派生类的析构函数中,只负责清除自己的成员:
~son() { cout << "son destructor" << endl; }
定义了son的析构函数之后,上述代码的结果是:
... son destructor father destructor son destructor father destructor son destructor father destructor
对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。
另外,如果基类指针指向了一个派生类对象,则删除该基类指针时,如果基类的析构函数不是虚函数的话,则只会调用基类的析构函数,而不会调用派生类析构函数。比如还是上面的father和son两个类,运行下面的代码:
father *fp = new son; delete fp;
结果就是:
father constructor son constructor father destructor
为了避免这种情况的发生,应该将基类的析构函数定义为虚函数,这样,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。从而可以得到正确的结果。
因此,即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。
3:构造函数或析构函数中调用虚函数
在运行构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。
因此,如果是在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。如果在基类构造函数中调用虚函数,则构造派生类的时候,该虚函数实际上还是调用的基类的版本:
class father { public: int publ_i; father(int a=1, int b=2, int c=3):publ_i(a),priv_i(b), prot_i(c) { cout << "father constructor" << endl; display(); } virtual void display() { cout << "[father]publ_i is " << publ_i << endl; cout << "[father]priv_i is " << priv_i << endl; cout << "[father]prot_i is " << prot_i << endl; } private: int priv_i; protected: int prot_i; }; class son: public father { public: son() { cout << "son constructor" << endl; } void display() { cout << "[son]publ_i is " << publ_i << endl; cout << "[son]prot_i is " << prot_i << endl; } }; int main() { father *fp = new son; }
在构造son对象时,需要调用father的构造函数,在father的构造函数中调用了虚函数display。此时,虽然构造的是派生类对象,但是这里依然还是调用基类的display函数。因此,上述代码的结果为:
father constructor [father]publ_i is 1 [father]priv_i is 2 [father]prot_i is 3 son constructor
无论由构造函数(或析构函数)直接调用虚函数,或者从构造函数(或析构函数)所调用的函数间接调用虚函数,都应用这种绑定。
要理解这种行为,考虑如果从基类构造函数(或析构函数)调用虚函数的派生类版本会怎么样。虚函数的派生类版本很可能会访问派生类对象的成员,但是,对象的派生部分的成员不会在基类构造函数运行期间初始化,实际上,如果允许这样的访问,程序很可能会崩溃。