C-默认构造函数什么情况下才真正被合成

C++ 默认构造函数什么情况下才真正被合成

​ 前提知识:C++ 默认构造函数为什么存在以及编译器怎么实现

​ 对于class X,如果没有任何user-declared constructor,那么就会有一个default constructor被隐式(implicitly)声明出来······ 一个被隐式声明出来的default constructor将是一个trivial(浅薄而无能,没啥用的)constructor······ ——摘自《深度探索C++对象模型》

​ 首先需要解读上面这句话,在类设计的过程,类成员中没有提供一个用户自定义的构造函数,编译器会隐式声明一个默认构造函数给类。但是这个默认构造函数中做了什么?是否对类的构造有作用?

​ 这是笔者一开始学习该知识过程中所不具备的知识。

什么是nontrivial constructor?

​ 如果一个class没有任何constructor,但它内涵一个member object,而后者有default constructor,那么这个class的implicit constructor就是"nontrivial(有用的)"

​ 编译器需要为该class合成一个default constructor。不过这个合成操作只有在constructor 真正需要被调用时才会发生

“带有Default Constructor” 的 Member Class Object

举个例子,在下面的程序片段中,编译器为 class Bar 合成一个 default construction:

class Foo { public: Foo(), Foo(int) };
class Bar { public: Foo foo; char* str; };
void foo_bar()
{
    Bar bar;	// Bar::foo 必须在此处初始化
    if ( str ) {  } ...
}

​ 被合成的Bar default constructor 内含必要的代码,能够调用class Foo 的 default constructor 来处理member object Bar::foo,但它并不产生任何代码来初始化Bar::str。因为在开发者角度上出发,Bar::foo初始化是编译器的责任,将Bar::str初始化则是程序员的责任。被合成的default constructor 看起来可能是这样的

inline
Bar::Bar()
{
	foo.Foo::Foo();
}

但在程序员定义的default constructor中

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

现在程序员的需求的满足了,但是编译器还需要初始化member object foo,由于default constructor 已经被现实定义出来,编译器无法合成第二个。

那么这个时候编译器的行动是

​ 如果class A内含一个或一个以上的member class objec,那么class A的每一个constructor 必须调用每一个member classes的default constructor

延续上诉的例子,扩张后的constructor可能是这样的

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

多个member class object,则按照声明顺序,按序初始化

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

“带有Default Constructor” 的 Base Class

与上述道理相似。

​ 如果一个没有任何constructors的class派生自一个"带有default constructor" 的 base class,那么这个derived class 的default constructor 会被视为nontrivial,并因此需要被合成出来。它将调用上一层base classes的default constructor(根据它的声明顺序)。对一个后继派生的class而言,这个合成的constructor 和 一个"被显示提供的default constructor" 没有什么差异

“带有一个Virtual Function” 的 Class

另外有两种情况,也需要合成default constructor:

  1. class声明(或继承)一个virtual function
  2. class派生自一个继承串链,其中有一个或更多的virtual base classes

如"带有一个Virtual Function" 的 Class这一前提

有两个扩张的行为会在编译期间发生:

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

故对于那些未声明任何constructions的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; }
//无法再编译期决定 pa->x::i 的位置
void foo( const A* pa ) { pa->i = 1024; }

操作需要在运行期才得以确认,为了符合这样的实现策略

//可能的编译器转变操作
void foo( const A* pa ) { pa->_vbcx->i = 1024; }

其中_vbcx表示编译器所产生的指针,指向virtual base class X

总结

在上述4种情况下的class 中没有声明constructor的时候,由于缺乏user声明的constructions,编译器都会生成一个nontrivial constructor,将详细记录合成一个default cons的必要信息。

所以C++ 新手一般有两个常见的误解:

  1. 任何class如果没有定义default constructor,就会被合成出一个来。
  2. 编译器合成出来的default constructor 会显式设定”class 内每一个data member的默认值

没有一个是真的!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值