Default Constructor的构造操作


前言

默认构造函数是C++对象中最基本的一类函数,如果一个类没有自定义任何构造函数,那么编译器会为这个类生成一个 implicit default constructor,只不过这是一个trivial(无能的,无用的) constructor。一个 nontrivial default constructor 在必要的时候会由编译器合成出来,以下是 nontrivial default constructor 的四种情况。


一、带有默认构造器的 member class object

如果一个类没有任何构造器,但它内含一个成员对象,这个成员对象拥有默认的构造器,那么这个类的隐式默认构造函数就是“nontrivial”,编译器需要为这个类合成出一个默认构造函数,不过这个合成操作只有在constructor真正需要被调用时才会发生。

由此衍生出一个问题,即如果在一个头文件中声明了一个类,如果这个头文件被多个cpp文件包括,那么编译器是如何避免重复定义默认构造函数呢?解决方法是把合成的 default constructor、copy constructor、destructor、assignment copy constructor 都以inline方式完成。因为一个inline函数有静态链接(static linkage),不会被文件以外者看到。
例如:

class Foo{
public:
	Foo(){}
	Foo(int) {...}
};
class Bar{
public:
    Foo foo;
    char *str;
};

void foo_bar(){
    Bar bar; // bar中的foo必须初始化,因为foo是一个成员对象,而其class Foo拥有默认构造器,符合本节主题
    if(str){...}
}

被合成的 Bar 的 default construction 内含必要的代码,能够调用 class Foo 的 default constructor 来处理成员变量 Bar::foo,但它并不产生任何代码来初始化 Bar::str,因为将 str 初始化是程序员的责任。
被合成出的 default constructor 可能是这样:

//Bar 的 default constructor 可能会被这样合成
// 为成员的 foo 调用 class Foo 的 defalut constructor
inline
Bar::Bar(){
	//c++伪码
	foo.Foo::Foo();
}

注意,合成的 default constructor 只满足编译器的需要,而不是程序员的需要。为了让这个程序片段能够正常执行,我们还需要 对 str 进行初始化,假设我们自己定义了一个 Bar 的 default constructor 如下:

Bar::Bar(){
	str = 0;
}

现在我们的要求被满足了,但是对编译器来说,foo 仍然需要初始化,又因为 default constructor 已经被显式地定义出来,编译器没有办法合成第二个,因为编译器的行动会是:

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

因此,上述的 default constructor 可能被编译器扩张为

Bar::Bar(){
	foo.Foo::Foo();
	str = 0;
}

如果有多个 class member objects 都要求 constructor 初始化操作,C++语言要求以“member objects 在 class 中声明顺序”来调用各个 constructors。这一点由编译器完成,这些代码将被安插在 explicit user code 之前。例如:

class Dopey{ public: Dopey(); };
class Sneezy{ public: Sneezy(); Sneezy(int); };
class Bashful{ public: Bashful(); };

class SnowWhite{
public:
	Dopey dopey;
	Sneezy sneezy;
	Bashful bashful;
	// ...
private:
	int number;
};

如果 SnowWhite没有定义 default constructor,就会有一个 nontrivial constructor 被合成出来,依序调用 Dopey、Sneezy、Bashful 的 default constructors。然而如果 SnowWhite 定义了下面这样的 default constructor:

SnowWhite::SnowWhite() : sneezy(1024){
	number = 2048;
}

它会被扩张为:

SnowWhite::SnowWhite() : sneezy(1024){
	//插入member class object
	//调用其 constructor
	depoy.Depoy::Depoy();
	sneezy.Sneezy::Sneezy(1024);
	bashful.Bashful::Bashful();
	
	//explicit user code
	number = 2048;
}

二、带有 Default Constructor 的 Base Class

类似的道理,如果一个没有任何构造器的 class 派生自一个带有默认构造器的 base class,那么这个派生类的默认构造器也会被视为 nontrivial,并因此需要被合成出来。它将调用上一层 base classes 的默认构造器(根据它们的声明顺序)。对于一个后继派生的 class 而言,这个合成的构造器和一个“被显式提供的默认构造器”没有什么差异。

如果设计者提供多个构造器,但其中都没有默认构造器,则编译器会扩张现有的每一个构造器,将其调用所有必要之默认构造器的代码加进去。它不会合成一个新的默认构造器。

如果同时存在成员对象,则成员对象的默认构造器也会被调用——在所有 base class 的构造器被调用之后。

三、带有一个 Virtual Function 的 Class

例如:

class Widget{
public:
	virtual void flip() = 0;
};

void flip(const Widget& widget){
	widget.flip();
}

void foo(){
	//假设 Bell 和 Whistle 都派生自 Widget
	Bell bell;
	Whistle whistle;
	
	flip(bell);
	flip(whistle);
}

下面两个扩张行动会在编译期间发生:

1.一个 virtual function table(vtbl)会被编译器产生出来,内放在 class 的 virtual functions 地址。
2.在每一个 class object 中,一个额外的 pointer member(也就是 vptr)会被编译器合成出来,内含相关之 class vtbl 的地址

对于 class 中定义每个构造器,编译器都会安插一些代码来做这样的事情。对于那些未声明任何构造器的 classes,编译器都会为它们合成一个 default constructor,以便正确地初始化每一个 class object 的 vptr。

四、带有一个 Virtual Base Class 的 Class

如下:

class X { public: int i; };
class A : public virtual X { public: int j; };
class B : public virtual X { public: double d; };
class C : public A, public B { public: int k; };

//无法在编译时期确定(resolve)出 pa->X::i 的位置
void foo(const A* pa){
	pa->i = 1024;
}

main(){
	foo(new A);
	foo(new C);
	//...
}

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

void foo(const A* pa){
	pa->___vbcX->i = 1024;
}

其中__vbcX 表示编译器所产生的指针,指向 virtual base class X,而这个指针就是在 class object 构造期间被完成的。即如果没有任何构造器,编译器必须为它合成一个默认构造器。


总结

有4种情况,会造成 ”编译器必须为未声明构造器的类合成一个默认构造器”,即 implicit nontrivial default constructors。被合成出来的构造器只满足编译器的需要。它之所以能够完成任务,是借着 “调用 member object 或 base class 的default constructor” 或是 “为每一个 object 初始化其 virtual function 机制或 virtual base class 机制” 而完成的。

至于没有存在这4种情况而又没有声明任何构造器的类,它们拥有的是 implicit trivial default constructors,它们实际上不会被合成出来。

在合成的 default constructor 中,只有 base class subobjects 和 member class objects 会被初始化。所有其他的 nonstatic data member 都不会被初始化。

以上类容总结于《深度探索C++对象模型》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值