【C++对象模型】构造函数-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

构造函数语意学

默认构造函数的构造操作

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:int value;};
class Bar{public:Foo foo; char* str}

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

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

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

如果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 A  public 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。


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++