[原]C++:子类的构造、拷贝和析构

本文仅考虑单继承的情况。——多继承复杂性比较高,暂不讨论。

考虑
class AA
{
// ...
private:
    TYPE m_1;
};
class BB: public AA
{
// ...
};

这种情况下,~AA()必须是virtual,原因不必多说——这是使用C++来作OO开发的best practice。这就引入了一个问题:虚表的处理。

首先观察一下C++的对象模型。对于含有虚函数的类来说,其成分有数据成员、成员函数、虚表、静态成员。而该类的一个实例仅仅包含数据成员和指向虚表的指针。对于成员函数和静态成员来说,所有的实例共享一个拷贝——成员函数是代码共享,而静态成员则是存储共享。

构造
对于AA,构造很简单:获取一块内存(栈/堆),初始化成员。注意ctor里有个误区:写在ctor中的“=”并非是初始化,而是拷贝/赋值。这也是需要初始化列表的原因。成员的构造函数有参数将更明显:
AA(TYPE _aTYPE): m_1(_aTYPE) {}
如果写成
AA(TYPE _aTYPE){ m_1 =_aTYPE; }
很明显,当TYPE提供了带参数的ctor时,第二种形式的构造就是错的。
如:

class AA
{
    public:
    AA (int a){cout << "ctor of AA" << endl;}
};
class BB 
{
    public:
    BB(){cout << "ctor of BB" << endl;}

    private:

    AA m_A;
};
int main()
{
    BB aBB;
    return 0;
}

编译时将会有错误:
no matching function for call to ‘AA::AA()’
candidates are: AA::AA(int)

说了点题外话。言归正传。

class AA
{
    public:
    AA (){cout << "ctor of AA" << endl;}
    virtual ~AA(){cout << "dtor of AA" << endl;}
};
class BB: public AA
{
    public:
    BB(){cout << "ctor of BB" << endl;}
    virtual ~BB(){cout << "dtor of BB" << endl;}
};
int main()
{
    BB aBB;
    return 0;
}

当构造子类BB的对象时,首先构造属于AA的部分。此时,调用AA的构造函数,并使vptr指向AA的虚表。这时存储区的类型是AA。然后,开始构造属于BB的部分,调用BB(),并使vptr指向BB的虚表。相当于:

AA (){__vptr = VTABLE_AA; cout << "ctor of AA" << endl;}


BB():AA(){__vptr = VTABLE_BB; cout << "ctor of BB" << endl;}


拷贝和赋值
有了前面的讨论,下面的内容就好理解了。
为了保证正确拷贝父类部分,拷贝构造和赋值操作符都需要调用父类的相应函数:

BB(const BB& aBB):AA(aBB) { cout <<"copy ctor of BB" << endl;}


析构
与构造相反。析构BB的实例时,先调用~BB(),将BB的部分析构掉,并使vptr指向AA的虚表。此时对象的类型为AA。然后才是调用~AA()。

关于构造,关键点在于BB(/*...*/){//...}中,“)”与“{”之间编译器插入了很多代码,这些代码在幕后做了很多工作;而关于析构,关键点在于~BB(){/*...*/},最后一条语句与“}”之间,编译器插入了很多作幕后工作的代码;对于拷贝和赋值,就需要显式加上对父类的拷贝和赋值的调用——因为默认的拷贝/赋值行为是按位进行的。

参考
Inside the C++ Object Model, by Stanly B Lippman
Effective C++, by Scott Meyers
The C++ programming Language, by Bjarne Stroustup

Copyleft (C) 2007-2009 raof01.
请保留此权利声明,并标明文章原始地址和作者信息
posted on 2008-11-12 16:53  raof01  阅读(881)  评论(0编辑  收藏  举报