对象模型-构造函数语意学

关于默认构造函数的误区:

  • 任何class如果没有定义default constructor,就会被合成出来一个

  • 编译器合成出来的default constructor会显式设定“class内每一个data member的默认值”

什么时候才会合成出default constructor,当编译器需要它的时候被合成出来,被合成出来的default constructor 只执行编译器所需的行动。言外之意,data member的初始化由程序员自己保证。

下面列出了几种被合成default constructor的情况

带有default constructor的member class object


一个class没有任何constructor,但它含有一个member object,而这个member object 有default constructor, 那么这个class的default constructor就是有用的(nontrivial),编译器需要为这个class 合成出一个default constructor, 不过这个合成操作只有在constructor真正需要被调用时才会发生

class Foo
{
public:
	Foo()
	{
		cout << "Foo" << endl;
	}
};
class Bar
{
public:
	Foo foo;
	char *m_pName;
};
int main()
{
	{
		Bar bar;
	}
	system("pause");
	return 0;
}

输出:

Foo

以上为例,被合成出来的Bar default constructor肯定内含了调用Foo default constructor函数的代码, 但是它并不产生任何代码初始化m_pName,合成Bar default constructor函数是编译器的责任,初始化m_pName是程序员的责任。

以上代码基础增加Bar default constructor:

public:
Bar()
{
	m_pName = nullptr;
}

现在Bar default constructor被显式的定义出来,编译器没办法合成第二个

这时编译器的做法:

如果一个类class内含有一个或一个以上的member class object,那么这个类class的每一个constructor必须调用每一个member class 的default constructor。编译器会扩张已存在的constructor,在其中user code之前安插一些代码,使得user code被执行之前,先调用必要的default constructors

安插之后代码可能是这样:

public:
Bar()
{
	foo.Foo::Foo();
	m_pName = nullptr;
}

如果有多个class member objects 都要求constructor初始化操作,则按照member object在class中的声明顺序来调用各个default constructor,这也由编译器完成,为每一个constructor安插程序代码。

示例:

` class A { public: A() { cout « “A” « endl; } }; class B { public: B() { cout « “B” « endl; } }; class C { public: C() { cout « “C” « endl; } }; class Alphabet { public: A m_objA; B m_objB; C m_objC; }; { Alphabet objAlphabet; } `

以上代码为例,类Alphabet并没有定义default constructor,编译器就会合成出一个default constructor,并且按照声明顺序依次调用 类A、B、C的default constructor。依次输出:

A
B
C

然而如果程序员编写了Alphabet的default constructor,如下:

Alphabet(): m_objC()
{
	cout << "Alphabet" << endl;
}

输出:

A
B
C
Alphabet

至于编译器安插的代码在default constructor函数体内还是在member initialization list我们以后讨论。从vs编译器来看是按照了声明的顺序依次调用member class object的default constructor

带有default constructor的Base Class


  • 如果一个没有任何constructor的class派生自一个带有default constructor的base class,那么这个derived class的default constructor会被视为有用的(nontrivial),并因此被编译器合成出来,他将调用上一层base class的default constructor(根据声明的顺序), 然而对于一个后继派生的class而言,这个合成的constructor和一个被显式声明的default constructor没什么差异。

  • 如果派生的class提供了多个constructors,但其中没有default constructor,编译器会扩张现有的每一个constructor,将安插“调用所有必要的default constructor”,但是编译器不会合成一个新的default constructor,如果类class同时也存在着“带有default constructor”的member class object(第1种情况),那些member class object的default constructor也会被调用,并且在所有base class constructor都被调用之后

示例:

class English
{
public:
	English()
	{
		cout << "English" << endl;
	}
};

class Alphabet : public English
{
public:
	A m_objA;
	B m_objB;
	C m_objC;
};
{
	Alphabet objAlphabet;
}

输出:

English
A
B
C

带有一个virtual function的class


以下两种情况也需要合成出default construtor

  • class 声明(或继承)一个virtual function

  • class 派生自一个继承串链,其中有一个或更多的virtual base classs

在编译期间有两个扩张发生

  • 一个virtual function table会被编译器产生出来

  • 在每个class object中,一个额外的指针指向vtbl(virtual function table)

所以必须为每个class object的vptr设定初值,放置适当的virtual table地址,对于class所定义的每一个constructor,编译器会安插一些代码来做这样的事情,对于那些没有声明任何constructors的class,编译器会为它们合成一个default constructor,以便正确的初始化每一个class object的vptr

带有一个virtual Base Class的class


virtual base class的实现法在不同的编译器之间有极大的差异。然而,每一种实现法的共同点在于必须使virtual base class在其每一个derived class object中的位置能够于执行期准备妥当。例如下面代码:

class X { public: int m_nId;};
class A : public virtual X { public: int m_nVarA;}
class B : public virtual X { public: int m_nVarB;}
class C : public A,public B { public: int m_nVarC;}

void foo(const A* pA) 
{ 
	pa->m_nId = 10;
}

{
	foo(new A);
	foo(new C);
}

编译器无法固定住foo()之中“经由pA而存取X::m_nId”的实际偏移位置,因为pA的真正类型可以改变, 编译器必须改变“执行存取操作”的代码,使X::m_nId可以延迟至执行期才决定下来。有的编译器的做法是在derived class object的每个 virtual base class中安插一个指针,所有经由reference或者pointer来存取一个virtual base class的操作都可以通过这个指针完成

// 可能的编译器转变操作
void foo(const A* pA) { pa->__vbcX->m_nId = 10;}

其中__vbcX指向virtual base class X,这个指针是在class object构造期间完成。对于class所定义的每个constructor,编译器会安插那些允许每一个virtual base class的执行期存取操作代码。而此时如果class没有声明任何constructor,编译器必须为它合成一个default constructor

最后


以上4中情况均会造成编译器必须为未声明的constructor的class合成一个default constructor,而c++ standard把那些合成物称为 implicit nontrivial default constructors,被合成出来的constructor只能满足编译器需要,除了以上4中情况而又没有声明任何constructor的classes,我们称为 implicit trivial default constructor,它们实际上并不会被合成出来

在合成的default constructor中,只有base class subobject和member class object会被初始化,所有其他的nonstatic data member都不会被初始化。这些初始化操作对程序而言或许有用,但对编译器而言则非必要。如果有需要初始化的nonstatic data member必须由程序员显示提供