【C++对象模型】构造函数

构造函数语意学

》》构造函数语意学I—默认构造函数的构造操作《《
》》构造函数语意学II—拷贝构造函数的构造操作《《
》》构造函数语意学III—程序转化语意学《《

默认构造函数的构造操作

C++Annotated Reference Manual告诉我们:“默认构造函数……在需要的时候被编译器产生出来”。关键字眼是“在需要的时候”。被谁需要?做什么事情?

看看下面这段程序代码:

class Foo{
public:
    int value;
    Foo* pnext;
};

void foo_bar()
{
    Foo bar;
    if(bar.value || bar.pnext)...
}

有一个默认构造函数,可以将它的两个members初始化为0。上面这段代码可曾符合ARM所说的“在需要的时候”?答案是 no。其间的差别在于一个是程序的需要,一是编译器的需要。程序如果有需要,那是程序员的责任;本例要承担责任的是设计class Foo的人

那么,什么时候才会合成出一个默认构造函数呢?当编译器需要它的时候!此外,被合成出来的构造函数只执行编译器所需的行动。也就是说,即使有需要为class Foo合成一个默认构造函数,那个构造函数也不会将两个val和pnext初始化为0。为了让上一段代码正确执行,class Foo的设计者必须提供一个显式的默认构造函数,将两个members适当地初始化。

“带有默认构造”的成员类对象

编译器需要为该 class 合成出一个默认构造函数。不过这个合成操作只有在构造函数真正需要被调用时才会发生。
在C++各个不同的编译模块中,编译器为了避免合成出多个默认构造函数(比如说一个是为A.C文件合成,另一个是为B.C文件合成)把合成的默认构造、拷贝构造、析构、赋值复制运算符都以inline方式完成。一个inline函数有静态链接,不会被文件以外者看到。如果函数太复杂,不适合做成inline,就会合成出一个explicit non-inline static实例

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

void foo_bar()
{
    Bar bar; //bar::foo在此处初始化
    if(str)...
}

被合成的Bar默认构造函数内含必要的代码,能够调用class Foo的默认构造函数来处理member object Bar::foo

将Bar::foo初始化是编译器的责任,将Bar::str初始化则是程序员的责任。
被合成的默认构造函数只满足编译器的需要,而不是程序的需要。

如果class A内含一个或一个以上的member class objects,那么 class A的每一个构造函数必须调用每一个member classes 的默认构造函数 。编译器会扩张已存在的构造函数,在其中安插一些代码
如果有多个class member objects都要求构造函数初始化操作,C++语言要求以“成员对象 在class中的声明顺序”来调用各个构造函数。

“带有默认构造函数”的 Base Class

如果一个没有任何构造函数的class派生自一个“带有默认构造函数”的基类,那么这个继承类的默认构造函数会被视为nontrivial(不平凡),并因此需要被合成出来。它将调用上一层base class的默认构造函数

如果设计者提供多个构造函数,但其中都没有默认构造函数呢?编译器会扩张现有的每一个构造函数,将“用以调用所有必要之默认构造函数”的程序代码加进去。

“带有一个虚函数”的Class

另有两种情况,也需要合成出默认构造函数:

  1. class声明(或继承)一个虚函数。
  2. class派生自一个继承串链,其中有一个或更多的虚基类。

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

  1. 一个 虚函数表(vtbl)会被编译器产生出来,内放 class的虚函数地址。
  2. 在每一个类对象中,一个额外的虚函数指针(vptr)会被编译器合成出来,内含相关之虚函数表的地址。

此外,基类的虚拟调用操作会被重新改写,以使用子类的vptr和vtbl中的条目为了让这个机制发挥功效,编译器必须为每一个基类的vptr设定初值,放置适当的虚函数表地址。对于class所定义的每一个构造函数,编译器会安插一些代码来做这样的事情

“带有一个虚基类”的 Class

//菱形继承
class X {public :int i;};
class A :public virtual X {public :int j;};
class B :public virtual X {public :double d;};
class B :public Apublic B{public :int k;};

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

编译器无法固定住fo()之中“经由pa而存取的X::i”的实际偏移位置,因为pa的真正类型可以改变。
foo()可以被改写如下,以符合这样的实现策略

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

其中__vbcX表示编译器所产生的指针,指向虚基类X。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值