Table of Contents

1.Default Constructor的建构操作

1.2 nontrivial default constructor四种情况

1.2.1“带有Default Constructor”的Member Class Object

1.2.2 “带有Default Constructor”的Base Class

1.2.3 “带有一个Virtual Function”的Class

1.2.4“带有一个Virtual Base Class”的Class

2.Copy Constructor的建构操作

2.1 Bitwise Copy Semantics(位逐次拷贝)

2.2 不要Bitwise Copy Semantics

2.2.1 重新设定Virtual Table的指针

2.2.2 处理Virtual Base Class Subobject

3.成员们的初始化队伍(Member Initialization List)

3.1 在initialization list中调用member function

3.2 调用member function初始化基类

4. Named Return Value(NRV)优化


1.Default Constructor的建构操作

1.1 C++ Annotated Reference Manual(ARM)中的Section12.1告诉我们:“default constructor...在需要的时候被编译器产生出来”。

class Foo { public: int val; Foo *pnext; };void foo_bar()
{// oops: 程序要求bar's members都被清为0Foo bar;if(bar.val || bar.pnext)// ... do something// ...
}

在这个例子中,正确的程序语意是要求Foo有一个default constructor,可以将它的两个members初始化为0. 上面这段代码可曾符合ARM所说的“在需要的时候”?答案是no。其间的差别在于一个是程序的需要,一个是编译器的需要。程序如果有需要,那是程序员的责任;本例要承担责任的是设计class Foo的人。是的,上述程序片段并不会合成出一个default constructor。

C++ Standard已经修改了ARM中的说法,虽然其行为事实上仍然是相同的。

对于class X, 如果没有任何user-declared constructor,那么会有一个default constructor被隐式地(implicitly)声明出来……

这个隐式地声明出来的constructor可能是trivial(没啥用的),也可能是nontrivial的。一个nontrivial default constructor在ARM的术语中就是编译器所需要的那种,必要的话会由编译器合成出来。

1.2 nontrivial default constructor四种情况

1.2.1“带有Default Constructor”的Member Class Object

如果一个class没有任何constructor,但它内含一个member object,而后者有default constructor,那么这个class的implicit default constructor就是“nontrivial”,编译器需要为此class合成一个default constructor。不过这个合成操作只有在constructor真正需要被调用时才会发生。

class Foo { public: Foo(), Foo(int) ... };
class Bar { public: Foo foo; char* str; };void foo_bar()
{Bar bar; // Bar::foo必须在此处初始化。Bar::foo是一个member object,而其class Foo拥有default constructorif(str) { } ...
}

编译器为class Bar合成一个default constructor。被合成的Bar default constructor内含必要的代码,能够调用class Foo的default constructor来处理member onject Bar::foo,但它并不产生任何代码来初始化Bar::str。是的,将Bar::foo初始化是编译器的责任,将Bar::str初始化则是程序员的责任。被合成的default constructor看起来可能像这样:

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

假设程序员经由下面的default constructor提供了str的初始化操作:

// 程序员定义的default constructor
Bar::Bar() { str = 0; }

但是编译器还需要初始化member object foo,编译器的行动是:“如果class A内含一个或一个以上的member class objects,那么class A的每一个constructor必须调用每一个member class的default constructor”。编译器会扩张已存在的constructor,在其中安插一些码,使得user code在被执行之前,先调用必要的default constructor。扩张后的constructor可能像这样:

// 扩张后的default constructor
// C++伪码
Bar::Bar()
{foo.Foo::Foo(); //附加上的compiler codestr = 0;        //explicit user code
}

如果有多个class member object都要求constructor初始化操作,C++语言要求以“member object在class中的声明次序”来调用各个constructor。这一点由编译器完成,它为每一个constructor安插程序代码,以“member声明次序”调用每一个member所关联的default constructor。这些代码将被安插在explicit user code之前。

1.2.2 “带有Default Constructor”的Base Class

如果一个没有任何constructor的class派生自一个“带有default constructor”的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。它将调用上一层base classes的default constructor(根据它们的声明次序)。

如果设计者提供多个constructor,但其中都没有default constructor呢?编译器会扩张现有的每一个constructor,将“用以调用所有必要的default constructor”的程序代码加进去。它不会合成一个新的default constructor,这是因为其它“由user所提供的constructor”存在的缘故。如果同时亦存在着“带有default constructor”的member class object,那些default constructor也会被调用——在所有base class constructor都被调用之后。

1.2.3 “带有一个Virtual Function”的Class

class Widget
{
public:virtual void flip() = 0;// ...
};void flip(const Widget& widget) { widget.flip(); }// 假设Bell和Whistle都派生自Widget
void foo()
{Bell b;Whistle w;flip(b);flip(w);
}

下面两个扩张操作会在编译期间发生:

1.一个virtual function table会被编译器产生出来,内放class的virtual functions地址;

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

此外,widget.flip()的虚拟引发操作(virtual invocation)会被重新改写,以使用widget的vptr和vtbl中的flip()条目:

//widget.flip()的虚拟引发操作(virtual invocation)的转变
(*widget.vptr[1])(&widget)

其中:

1表示flip()在virtual table中的固定索引;

&widget代表要交给“被调用的某个flip()函数实体”的this指针。

为了让这个机制发挥功效,编译器必须为每一个Widget(或其派生类)object的vptr设定初值,放置适当的virtual table地址。对于class所定义的每一个constructor,编译器会安插一些代码来做这样的事情。对于那些未声明任何constructor的class,编译器会为它们合成一个default constructor,以便正确地初始化每一个class object的vptr。

1.2.4“带有一个Virtual Base Class”的Class

Virtual base class的实现法在不同的编译器之间有极大地差异。然而,每一种实现法的共同点在于必须使virtual base class在其每一个derived class object中的位置,能够在运行时准备妥当。

对于class所定义的每一个constructor,编译器会安插那些“允许每一个virtual base class的运行时存取操作”的代码。如果class没有声明任何constructor,编译器必须为它合成一个default constructor。

对于没有存在以上四种情况而又没有声明任何constructor的class,我们说它们拥有的是implicit trivial default constructor,它们实际上并不会被合成出来。

在合成的default constructor中,只有base class subobjects和member class objects会被初始化。所有其他的nonstatic data member,如整数、整数指针、整数数组等等都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则并非必要。如果程序需要一个“把某指针设为0”的default constructor,那么提供它的人应该是程序员。

2.Copy Constructor的建构操作

Default constructor和copy constructor在必要的时候才由编译器产生出来。

这个句子中的“必要”意指当class不展现bitwise copy semantics时。

就像default constructor一样,C++ Standard上说,如果class没有声明一个copy constructor,就会有隐含的声明(implicitly declared)或隐含的定义(implicitly defined)出现。和以前一样,C++ Standard把copy constructor区分为trivial和nontrivial两种。只用nontrivial的实体才会被合成于程序之中。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的“bitwise copy semantics”。

2.1 Bitwise Copy Semantics(位逐次拷贝)

#include "Word.h"Word noun("book");void foo()
{Word verb = noun;// ...
}

很明显verb是根据noun来初始化。但是在尚未看过class Word的声明之前,我们不可能预测这个初始化操作的程序行为。如果class Word的设计者定义了一个copy constructor,verb的初始化操作会调用它。但如果该class没有定义explicit copy constructor,那么是否会有一个编译器合成的实体被调用呢?这就得视该class是否展现“bitwise copy semantics”而定。

// 以下声明展现了bitwise copy semantics
class Word
{
public:Word(const char*);~Word() { delete [] str; }// ...
private:int cnt;char* str;
};

这种情况下并不需要合成出一个default copy constructor,因为上述声明展现了“default copy semantics”,而verb的初始化操作也就不需要以一个函数调用收场。

// 以下声明并未展现出bitwise copy semantics
class Word
{
public:Word(const String&);~Word();// ...
private:int cnt;String str;
};// String声明了一个explicit copy constructor:
class String
{
public:String(const char*);String(const String&);~String();// ...
};

在这个情况下,编译器必须合成出一个copy constructor以便调用member class String object的copy constructor:

// 一个被合成出来的copy constructor
// C++伪码
inline Word::Word(const Word& wd)
{str.String::String(wd.str);cnt = wd.cnt;
}

有一点很值得注意:在这被合成出来的copy constructor中,如整数、指针、数组等等的nonclass members也都会被复制,正如我们所期待的一样。

2.2 不要Bitwise Copy Semantics

什么时候一个class不展现出“bitwise copy semantics”呢?有四种情况:

1>当class内含一个member object而后者的class声明有一个copy constructor时(不论是被class设计者明确地声明,就像前面的String那样;或是被编译器合成,像class Word那样);

2>当class继承自一个base class而后者存在有一个copy constructor时(再次强调,不论是被明确声明或是被合成而得);

3>当class声明了一个或多个virtual functions时;

4>当class派生自一个继承串链,其中有一个或多个virtual base classes时。

前两种情况中,编译器必须将member或base class的“copy constructor调用操作”安插到被合成的copy constructor中。3>和4>两种情况后面展开讨论。

2.2.1 重新设定Virtual Table的指针

只要一个class声明了一个或多个virtual functions,编译器就会增加一个virtual function table,并将一个指向virtual function table的指针安插在每一个class object内。当编译器导入一个vptr到class之中时,该class就不再展现bitwise semantics了。

class ZooAnimal
{
public:ZooAnimal();virtual ~ZooAnimal();virtual void draw();
private:// ...
};class Bear: public ZooAnimal
{
public:Bear();void draw(); // 虽未明写virtual,它其实是virtual
private:// ...
};

ZooAnimal class object以另一个ZooAnimal class object作为初值,或Bear class object以另一个Bear class object作为初值,都可以直接靠“bitwise copy semantics”完成。

Bear yogi;
Bear winnie = yogi;

yogi会被default Bear constructor初始化。而在constructor中,yogi的vptr被设定为Bear class的virtual table(靠编译器安插的代码完成)。因此,把yogi的vptr值拷贝给winnie的vptr是安全的。

当一个base class object以其derived class的object内容做初始化操作时,其vptr赋值操作也必须保证安全,

ZooAnimal franny = yogi; // 这会发生切割(sliced)行为

franny的vptr不可以被设定指向Bear class的virtual table(但如果yogi的vptr被直接“bitwise copy”的话,就会导致此结果),而应该设定指向ZooAnimal的virtual table。

也就是说,合成出来的ZooAnimal copy constructor会明确设定object的vptr指向ZooAnimal class的virtual table,而不是直接从右手边的class object中将其vptr现值拷贝过来。

2.2.2 处理Virtual Base Class Subobject

Virtual base class的存在需要特别处理。一个class object如果以另一个object作为初值,而后者有一个virtual base class subobject,那么也会使“bitwise copy semantics”失效。

每一个编译器对于虚拟继承的支持承诺,都表示必须让“derived class object中的virtual base class subobject位置”在执行期就准备妥当。维护“位置的完整性”是编译器的责任。“Bitwise copy semantics”可能会破坏这个位置,所以编译器必须在它自己合成出来的copy constructor中做出仲裁。

class Raccoon: public virtual ZooAnimal
{
public:Raccoon() { ... }Raccoon(int val) { ... }private:// ...
};class RedPanda: public Raccoon
{
public:RedPanda() { ... }RedPanda(int val) { ... }private:// ...
};

编译器所产生的的代码(用以调用ZooAnimal的default constructor、将Raccoon的vptr初始化,并定位出Raccoon中的ZooAnimal subobject)被安插在两个Raccoon constructor之内,成为其先头部队。

再强调一次,如果以一个Raccoon object作为另一个Raccoon object的初值,那么“bitwise copy”就绰绰有余了。然而,如果企图以一个RedPanda object作为little_critter的初值,编译器必须判断“后续当程序员企图存取其ZooAnimal subobject时是否能够正确地执行”:

// 简单的bitwise copy还不够,
// 编译器必须明确地将little_critter的
// virtual base class pointer/offset初始化RedPanda little_red;
Raccoon little_critter = little_red;

在这种情况下,为了完成正确的little_critter初值设定,编译器必须合成一个copy constructor,安插一些代码以设定virtual base class pointer/offset的初值(或只是简单地确定它没有被抹消),对每一个members执行必要的memberwise初始化操作,以及执行其它的内存相关工作。

在下面的情况下,编译器无法知道是否“bitwise copy semantics”还保持着,因为它无法知道(没有流程分析)Raccoon指针是否指向一个真正的Raccoon object,或是指向一个derived class object:

//简单的bitwise copy可能够用,也可能不够用
Raccoon *ptr;
Raccoon little_critter = *ptr;

3.成员们的初始化队伍(Member Initialization List)

下列情况中,为了让程序能够被顺利编译,必须使用member initialization list:

1>当初始化一个reference member时;

2>当初始化一个const member时;

3>当调用一个base class的constructor,而它拥有一组参数时;

4>当调用一个member class的constructor,而它拥有一组参数时。

下面的例子可以被正确编译并执行,但是效率不高,

class Word
{string _name;int _cnt;
public:Word(){_name = 0;_cnt = 0;}
};// constructor可能的内部扩张结果:
// C++ 伪码
Word::Word( /* this pointer goes here */ )
{// 调用 String的default constructor_name.String::String();// 产生暂时性对象String temp = String(0);// "memberwise"地拷贝 _name_name.String::operator=(temp);// 摧毁暂时性对象temp.String::~String();_cnt = 0;
}

一个明显更有效率的实现方法:

// 较佳的方式
Word::Word() : _name(0)
{_cnt = 0;
}// 可能的扩张结果
// C++伪码
Word::Word( /* this pointer goes here */ )
{// 调用 String(int) constructor_name.String::String(0);_cnt = 0;
}

intialization list中的项目次序是由class中的members声明次序决定的,不是由initialization list中的排列次序决定的。

3.1 在initialization list中调用member function

// X:xfoo() 被调用,这样好吗
X::X(int val): i(xfoo(val)), j(val)
{ }

能否调用一个member function设定一个member的初值?其中xfoo()是X的一个member function。答案是yes,但是请使用“存在于constructor体内的一个member”,而不要使用“存在于member initialization list中的member”,来为另一个member设定初值。你并不知道xfoo()对X object的依赖性有多高,如果你把xfoo()放在constructor体内,那么对于“到底是哪一个member在xfoo()执行时被设立初值”这件事,就可以确保不会发生模棱两可的情况。

Member function的使用是合法的(当然我们必须不考虑它所用到的members是否已初始化),这是因为和此object相关的this指针已经被构建妥当,而constructor大约被扩充为:

// C++伪码:constructor被扩充后的结果
X::X(/* this pointer, */ int val)
{i = this->xfoo(val);j = val;
}

3.2 调用member function初始化基类

如果一个derived class member function被调用,其返回值被当做base class constructor的一个参数,将会如何:

// 调用 FooBar::fval() 可以吗
class FooBar : public X
{int _fval;
public:int fval() { return _fval; }FooBar(int val): _fval(val), X(fval()){ }// ...
};// 可能的扩张结果
// C++ 伪码
FooBar::FooBar( /* this pointer goes here */ )
{// oops: 实在不是一个好主意X::X( this, this->fval() );_fval = val;
}

它的确不是一个好主意。

总结,编译器会对initialization list一一处理并可能重新排序,以反映出members的声明次序。它会安插一些代码到constructor体内,并置于任何explicit user code之前。

4. Named Return Value(NRV)优化

在一个如bar()这样的函数中,所有的return指令传回相同的具名数值(named value),因此编译器有可能自己做优化,方法是以result参数取代named return value。例如下面的bar()定义:

X bar()
{X xx;// ...处理 xxreturn xx;
}编译器把其中的xx以__result取代:
void
bar(X& __result)
{// default constructor 被调用// C++ 伪码__result.X::X();// ... 直接处理 __resultreturn;
}

这样的编译器优化操作,有时候被称为Named Return Value(NRV)优化。

[读书笔记] - 《深度探索C++对象模型》第2章 构造函数语意学相关推荐

  1. 第2章构造函数语义学读书笔记——深度探索c++对象模型

    深度探索c++对象模型 第2章 构造函数语义学 2.1 Default Constructor的构建操作 2.2 Copy Constructor的构造操作 2.3 程序转化语义学 2.4 成员的初始 ...

  2. 深度探索C++对象模型第2章 构造函数语义学

    默认构造函数 两个误区: 1 任何class如果没有定义默认构造函数,就会被合成一个出来:只有在某些情况下被合成 2 编译器合成出来的默认构造函数会明确设定class中每一个数据成员的默认值 :默认值 ...

  3. C++-----深度探索C++对象模型-第四章-Function语意学(二)

    1.多态对象有某种形式执行期类型判断法,多态其实就是使用一个基类指针寻址出一个派生类对象的意思. 2.识别一个class是否支持多态,唯一的方法就是看它是否有任何虚函数. 1)编译期,找到虚函数表,每 ...

  4. 读书笔记-深度学习入门之pytorch-第四章(含卷积神经网络实现手写数字识别)(详解)

    1.卷积神经网络在图片识别上的应用 (1)局部性:对一张照片而言,需要检测图片中的局部特征来决定图片的类别 (2)相同性:可以用同样的模式去检测不同照片的相同特征,只不过这些特征处于图片中不同的位置, ...

  5. 读书笔记-深度学习入门之pytorch-第五章(含循环实现手写数字识别)(LSTM、GRU代码详解)

    目录 1.RNN优点:(记忆性) 2.循环神经网络结构与原理 3.LSTM(长短时记忆网络) 4.GRU 5.LSTM.RNN.GRU区别 6.收敛性问题 7.循环神经网络Pytorch实现 (1)R ...

  6. 《深入探索C++对象模型》第二章 构造函数语义学(The Semantics of Constructors)

    一.default constructor的构造操作 先看一个小例子: class Foo { public: int val; Foo *pnext; }; 此处正确的程序语意是要求Foo有一个默认 ...

  7. 【C++对象模型】第二章 构造函数语意学

    1.Default Constructor 当编译器需要的时候,default constructor会被合成出来,只执行编译器所需要的任务(将members适当初始化). 1.1  带有 Defau ...

  8. 《深度探索C++对象模型》读书笔记第五章:构造析构拷贝语意学

    <深度探索C++对象模型>读书笔记第五章:构造析构拷贝语意学 对于abstract base class(抽象基类),class中的data member应该被初始化,并且只在constr ...

  9. [读书笔记]《深度探索C++对象模型》

    文章目录 前言 思维导图 第一章 关于对象 第二章 构造函数语意学 构造函数 拷贝构造函数 初始化列表 第三章 Data 语意学 第四章 Function 语意学 非静态成员函数 静态成员函数 虚成员 ...

  10. 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记

    来源:http://dsqiu.iteye.com/blog/1669614 之前一直对C++内部的原理的完全空白,然后找到<Inside The C++ Object Model>这本书 ...

最新文章

  1. R语言KMeans聚类模型示例
  2. java类接口的区别_【Java基础】java接口和类的区别-瑶瑶吖的回答
  3. 项目经理如何参与任务管理
  4. 报名|PMCAFF原创专栏作者百人计划
  5. sql共享功能目录无法更改_大企业数据库服务首选!AliSQL这几大企业级功能你不可不知...
  6. 两种方法解决tomcat的 Failed to initialize end point associated with ProtocolHandler [http-apr-8080]...
  7. java中日历类:Calendar
  8. Java JSON库Jackson 2 x新变化一览
  9. 史上最权威的 Activiti 框架学习
  10. Mysql存储引擎之TokuDB以及它的数据结构Fractal tree(分形树)
  11. 3d模型多怎么优化_近似模型之响应面建模
  12. 求实数的整数部分和小数部分python_python求实数的整数部分
  13. 使用腾讯云paas服务接口通过视频进行活体校验-人脸识别
  14. word文档密码破解
  15. 用计算机弹的数字,在计算器上弹两只老虎是用那几个数字
  16. 通过mtd读写flash_【转】 Linux下读写FLASH驱动——MTD设备分析
  17. noip2014 珠心算测验 (枚举)
  18. a href标签下载文件遇到下载失败 打开文件的问题
  19. ARDUINO:控制两台步进电机同步运转
  20. LENOVO服务器批量升级BMC固件

热门文章

  1. pandas Dataframe统计缺失值占比
  2. 无人驾驶服务器适合部署在哪个位置,手机位置服务器在哪里设置的
  3. cv2.error: opencv(4.4.0)_【从零学习OpenCV 4】图像金字塔
  4. creo数控编程怎么样_邹军:通过数控宏程序实现刀具寿命管理
  5. BZOJ 4992: [Usaco2017 Feb]Why Did the Cow Cross the Road
  6. TX2017秋招笔试题之编码
  7. windows下python SSH-Client模块paramiko的安装与修改
  8. BZOJ 4143: [AMPPZ2014]The Lawyer( sort )
  9. BeginInvoke之前检测句柄
  10. 程序员的幽默--火车