构造、析构、拷贝语意学
抽象基类仍然需要一个显式的构造函数以初始化成员变量。
纯虚函数的存在
纯虚函数可以被静态调用,不可以通过虚拟机制调用。
但是纯虚析构函数一定要被定义。因为对于派生类来说,总要调用基类的析构函数,基类不定义析构函数会导致连接失败。
一 “无继承”情况下的对象构造
Plain OI’ Data类型:C风格结构体类型,无需构造和析构的过程,直接申请内存并复制内容就可以。
C中的全局/静态数据区中的数据会被分为初始化的和未初始化的;而C++中的全局变量全都是初始化过的,因为C++会有隐式构造的过程。
二 继承体系下的对象构造
构造函数的扩充步骤为:
- 调用虚基类构造函数,从左到右,从上往下
- 类如果在成员初始化列表中,则显式指定的参数都应该传过去,不在初始化列表中的类,调用其默认构造函数
- 类中每一个虚基类子对象的偏移位置(offset)必须在执行期是可取的
- 类对象是最底层的派生类,其构造函数可能被调用
- 按照从上到下的顺序调用基类构造函数
- 如果基类在初始化列表中显式指定,那么任何显式指定的参数都应该传进去
- 如果基类不在初始化列表中,但有默认构造函数的话,那么就调用这个默认构造函数构造基类
- 如果基类是多重继承下的第二或后继的基类,那么this指针要偏移做调整
- 如果类对象有虚表指针,初始化指向适当的虚函数表
- 函数初始化列表中的成员变量的初始化操作会被放入构造函数中
- 如果有成员不在初始化列表中,并且其有默认构造函数,那么默认构造函数必须被调用
- 执行程序员提供的代码
一个复合类的初始化过程如下:
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 虚函数表初始化语意学
当定义一个派生类对象的时候,类的构造函数的调用顺序是从上层到下层,按照继承顺序从左到右。
三 对象复制语意学
一个类对于默认的拷贝构造函数,在以下情况,不会表现出按位拷贝的语意。
- 当类中含一个类对象,而类有一个赋值运算符
- 当类的基类有一个赋值运算符
- 当一个类声明了任何虚函数,这时候不可以拷贝右边类对象的虚函数地址,因为其可能是派生类
- 类继承自虚基类
P.S. 虚基类最好不要进行拷贝操作,虚基类子对象的拷贝容易导致多次拷贝。
四 析构语意学
如果类没有定义析构函数,那么只有在类内部有类对象且此类有析构函数,或者这个类的基类有析构函数,编译器才会合成一个这个类的析构函数。否则的话不会合成析构函数。
析构函数的执行顺序一般是
- 析构函数的函数本体首先被执行
- 如果类有类对象成员,而类对象有析构函数,那么他们会以其声明顺序的相反顺序被调用
- 如果对象内含有一个虚表指针,现在被重新设定,指向适当的基类的虚函数表
- 如果有上一层的非虚基类有析构函数,那么会以声明顺序相反的顺序执行
- 如果有任何虚基类有析构函数,且当前类是继承链的最底层派生类,那么它们会以原来构造顺序的相反顺序被调用