深度探索C++对象模型-第五章

构造、析构、拷贝语意学

抽象基类仍然需要一个显式的构造函数以初始化成员变量。

纯虚函数的存在

纯虚函数可以被静态调用,不可以通过虚拟机制调用。

但是纯虚析构函数一定要被定义。因为对于派生类来说,总要调用基类的析构函数,基类不定义析构函数会导致连接失败。

一 “无继承”情况下的对象构造

Plain OI’ Data类型:C风格结构体类型,无需构造和析构的过程,直接申请内存并复制内容就可以。

C中的全局/静态数据区中的数据会被分为初始化的和未初始化的;而C++中的全局变量全都是初始化过的,因为C++会有隐式构造的过程。

二 继承体系下的对象构造

构造函数的扩充步骤为:

  1. 调用虚基类构造函数,从左到右,从上往下
    • 类如果在成员初始化列表中,则显式指定的参数都应该传过去,不在初始化列表中的类,调用其默认构造函数
    • 类中每一个虚基类子对象的偏移位置(offset)必须在执行期是可取的
    • 类对象是最底层的派生类,其构造函数可能被调用
  2. 按照从上到下的顺序调用基类构造函数
    • 如果基类在初始化列表中显式指定,那么任何显式指定的参数都应该传进去
    • 如果基类不在初始化列表中,但有默认构造函数的话,那么就调用这个默认构造函数构造基类
    • 如果基类是多重继承下的第二或后继的基类,那么this指针要偏移做调整
  3. 如果类对象有虚表指针,初始化指向适当的虚函数表
  4. 函数初始化列表中的成员变量的初始化操作会被放入构造函数中
  5. 如果有成员不在初始化列表中,并且其有默认构造函数,那么默认构造函数必须被调用
  6. 执行程序员提供的代码

一个复合类的初始化过程如下:

class A
{
public:
    A(int a = 0, int b = 0);
    A(const A &);
    A &operator=(const A &);

    virtual ~A();
    virtual int getz(){ return 0; }
protected:
    int x, int y;
};

class B
{
    A begin, end;
public:
    B(int a =0, int b = 0, int c = 0, int d = 0);
    B(const A &, const A &);
};

每一个显式构造函数都会被扩充以调用两个成员类对象的构造函数。假设构造函数如下:

B::B(const A &b, const A &e)
    :end(e), begin(b){ }

会被编译器扩充转换为:

B* B::B(B *this, 
        const A &b, const A &e)
{
    this->begin.A::A(begin);    //静态调用基类构造函数
    this->end.A::A(end);
    return this;
}

同样的,虚构函数中也会调用内部类对象的析构函数。

1 虚拟继承

和普通继承不一样的是,虚继承中没有构造函数的扩充情况出现。这是因为虚基类的“共享性”原因。

class A
{
public:
    A(int _a = 0) : a(_a){ }
    A(const A &_a) : a(_a.a){ }
    //虚函数
    ...
protected:
    int a;
};

class B : public virtual A
{
public:
    B(int _a = 0, int _b = 0) : A(_a), b(_b){ }
    B(const B &_b) : A(_b), b(_b.b){ }
    ~B(){ }
    B &operator=(const &);
    virtual int getb(){ return b; }
protected:
    int b;
};

这里B的构造函数会被扩充为这样:

B* B::B(B *this
        int _a, int _b)
{
    this->A::A(a);  //基类构造
    this->_vptr_B = _vptl_B;    //虚函数表指针
    this->_vptr_B_point = _vptl_B_point;
    this->b = _b;
    return this;
}

如果虚基类每一个派生类都对基类做初始化,会出现问题,因为所有的派生类都会对同一个基类做构造。解决方法是由最底层的派生类去做虚基类的初始化,所有的类的构造函数中除了this函数都还会引入一个bool变量用于判断是否构造虚基类,最底层的派生类构造虚基类后,会将变量置为false,这样所有的中间类都不会再构造虚基类。

2 虚函数表初始化语意学

当定义一个派生类对象的时候,类的构造函数的调用顺序是从上层到下层,按照继承顺序从左到右。

三 对象复制语意学

一个类对于默认的拷贝构造函数,在以下情况,不会表现出按位拷贝的语意。

  1. 当类中含一个类对象,而类有一个赋值运算符
  2. 当类的基类有一个赋值运算符
  3. 当一个类声明了任何虚函数,这时候不可以拷贝右边类对象的虚函数地址,因为其可能是派生类
  4. 类继承自虚基类

P.S. 虚基类最好不要进行拷贝操作,虚基类子对象的拷贝容易导致多次拷贝。

四 析构语意学

如果类没有定义析构函数,那么只有在类内部有类对象且此类有析构函数,或者这个类的基类有析构函数,编译器才会合成一个这个类的析构函数。否则的话不会合成析构函数。

析构函数的执行顺序一般是

  1. 析构函数的函数本体首先被执行
  2. 如果类有类对象成员,而类对象有析构函数,那么他们会以其声明顺序的相反顺序被调用
  3. 如果对象内含有一个虚表指针,现在被重新设定,指向适当的基类的虚函数表
  4. 如果有上一层的非虚基类有析构函数,那么会以声明顺序相反的顺序执行
  5. 如果有任何虚基类有析构函数,且当前类是继承链的最底层派生类,那么它们会以原来构造顺序的相反顺序被调用

猜你喜欢

转载自blog.csdn.net/u012630961/article/details/80528920