C++新手一般由两个常见的误解:

  • 如果任何class没有定义默认构造函数(default constructor),编译器就会合成一个来。
  • 编译器合成的的default constructor会显示的设定“class内每一个data member的默认值”

一、编译器在哪种情况下才会合成默认构造函数:

对于未声明构造函数的类,只有在以下四种情况下编译器才会为它们合成默认构造函数:

  • 类的成员有一个类对象(Member Class Object),且该成员含有默认构造函数(default Constructor)
  • 继承自带有默认构造函数(default constructor)的基类(Base class)
  • 带有虚函数(virtual function)的类
  • 继承自虚基类(virtual base class)的类

  对于以上四种情况,C++标准把合成的默认构造函数叫隐式的有意义默认构造函数(implicit nontrivial default constructors)。被合成的构造函数只能满足编译器(而非程序)的需要,它之所以能够完成任务,是借着调用成员对象或基类的默认构造函数(情况1/2),或是为每一个对象初始化其虚函数机制或虚基类机制(情况3/4)。

  至于没有存在上述四种情况,而又没有声明任何构造函数的类,那么它们拥有的是隐式无意义默认构造函数implicit trivial default constructors),实际上它们并不会被合成出来。

  在合成的默认构造函数中,只有basec class subobject(基类实例)、member class objects(成员类对象)会被初始化,其他的nonstatic data member(如整数、整数指针、整数数组等)都不会被初始化,初始化这些东西或许对程序而言是非常重要的,但是对于编译器来说则不是必要的。如果程序需要一个“把某指针设置为0”的默认构造函数,那么提供的它的人应该是程序员。


二、情况一:“带有Default Constructor”的Member Class Object

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

  于是出现了一个有趣的问题:在C+各个不同的编译模块中(不同的文件),编译器如何避免合成出多个 default constructor(比如说一个是为A.C文件合成,另一个是为B.C文件合成)呢?       解决方法是把合成的 default constructor、constructor、 destructor、 assignment copy operator都以 inline方式完成。一个inline函数有静态链接( static linkage),不会被文件以外者看到。如果函数太复杂,不适合做成 inline,那就会合成出一个 explicit non-inline static实例。

 //编译器为 class Bar合成一个 default
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 constructor,符合本小节主题
if( str ) { }... }

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

  被合成的默认构造函数如下所示:

//Bar 的默认构造函数可能会被合成如下形式:
//为成员foo调用类Foo的默认构造函数
inline Bar::Bar()
{
foo.Foo::Foo(); }

  不过合成的默认构造函数只是满足编译器的需要,为了能够让程序顺利执行,字符指针str也需要被初始化。假如我们用下面的构造函数为str提供了初始化:

//程序员定义的默认构造函数
Bar::Bar() {str = ;}

那么问题就来了,既然程序员自己显示定义了默认构造函数,编译器就不会再定义第二个(温饱已经解决,就不能再吃救急粮),那么编译器是怎么做的呢?

通过扩张程序员自己定义的构造函数,在其之前先调用成员类的默认构造函数,如果有多个类成员对象都要求constructor初始化操作,则将按成员对象在类内的声明顺序来调用各个成员类的构造函数,如下所示:

 class Dopey { public : Dopey();...};
class Sneezy {public : Sneezy (int); Sneezy();...};
class Bashful {public : Bashful(); ...}; //class Snow_White内含上述三个类:
class Snow_White{ public:
Dopey dopey;
Sneezy sneezy;
Bashful bashful;
private:
int mumble;
} //如果程序员没有定义默认构造函数,则编译器会自动合成 implicit nontrivial default constructor,其调用顺序按上述声明;
//如果程序员定义了如下的构造函数: Snow_White::Snow_White() : sneezy()
{
mumble = ;
} //那么编译器会将上述构造函数自动扩展成如下所示:
Snow_White::Snow_White() : sneezy()
{
//隐式加入member class object 的default constructors
dopey.Dopey::Dopye();
sneezy.Sneezy::Sneezy() ;
bashful.Bashful::Bashful(); mumble = ;
}

三、“带有Default Constructor”的基类:

  如果一个没有任何构造函数的类派生自一个带有default constructor的基类,那么这个派生类的默认构造函数被视为nontravial,它会调用上一层基类的默认构造函数,扩张性如上例所同。如果类中存在类成员,那么基类的默认构造函数放在成员的默认构造函数之前,接着是用户自己定义的默认构造函数部分。


四、带有一个Virtual Function的Class:

  如果类中声明或继承了一个虚函数(virtual function),或者类派生自一个继承串链,其中有一个或更多的虚基类。(virtual base classes)

class Widget {
public :
virtual void flip() = ; //声明了一个虚函数
}; void flip(const Widget &widget) {widget.flip();} //假设 Bell 和Whistle都派生自Widget
void foo()
{
Bell b; //Bell也继承了虚函数flip
Whistle w; //Whistle也继承了虚函数flip flip(b);
flip(w);
}

  构造函数中会发生以下两个扩张行为:

  • 编译器会为每个类产生一个virtual function table(虚函数表,vtbl)用来存放类的虚函数地址。
  • 编译器会为每个类中合成一个额外的指针成员(vptr),vptr指向类虚函数表的地址。

此外widget.flip()的虚拟调用操作会被重写,以使用widget中的vptr和vtbl的flip()条目。

// widget.flip()的虚拟调用操作的转变

 (*widget.vptr[])(&widget) //1表示flip()在virtual table中的固定索引
&wdget代表交给“被调用的某个flip()实例的”this指针

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


五、带有一个Virtual Base Class的类:

  编译器必须使virtual base class(虚基类)在每一个derived class object(派生类对象)的位置,能够在执行期准备妥当。

 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}; void foo(const A *pa) {pa->i = ; } main()
{
foo(new A);
foo(new C); }

  编译器无法固定住foo之中“经由pa而存取的x”的实际偏移位置,因为pa的真正类型可以改变。编译器必须改变“执行存取操作”的那些代码,使X::i可以延迟至执行期才决定下来。

  原先 cfront的做法是靠“在 derived class object的每一个 virtual base classes中安插一个指针”完成。所有“经由 reference或 pointer来存取一个 virtual base class”的操作都可以通过相关指针完成。在我的例子中,foo()可以被改写如下,以符合这样的实现策略:

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

  其中_vbcx表示编译器所产生的指针,指向virtual base class X; _vbcX是在类对象构造期间被完成的。对于类所定义的每个构造函数,编译器会安插那些“允许虚基类执行期存取操作”的代码,如果class没有声明构造函数,则编译器必须为他合成。

深度探索C++对象模型之第二章:构造函数语意学之Default constructor的构造操作的更多相关文章

  1. 《深度探索C++对象模型》第二章 | 构造函数语意学

    默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一 ...

  2. 深度探索C++对象模型之第二章:构造函数语意学之成员初始值列表

    当我们需要设置class member的初值时,要么是经过member initialization list ,要么在construcotr内. 一.先讨论必须使用member initializa ...

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

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

  4. 深度探索C++对象模型之第二章:构造函数语意学之Copy constructor的构造操作

    C++ Standard将copy constructor分为trivial 和nontrivial两种:只有nontrivial的实例才会被合成于程序之中.决定一个copy constructor是 ...

  5. 《深度探索C++对象模型》第一章 | 关于对象

    C++对象模式 非静态数据成员放置在每个类对象内,静态数据成员则被放置在所有类对象之外.静态和非静态的成员函数也被放置在所有类对象之外.每个类产生一堆指向虚函数的指针,放在虚表(vtbl)中.每个类对 ...

  6. 深度探索C++对象模型之第一章:关于对象之C++对象模型

    一.C和C++对比: C语言的Point3d: 数据成员定义在结构体之内,存在一组各个以功能为导向的函数中,共同处理外部的数据. typedef struct point3d { float x; f ...

  7. 深度探索C++对象模型之第一章:关于对象之对象的差异

    一.三种程序设计范式: C++程序设计模型支持三种程序设计范式(programming paradiams). 程序模型(procedural model) char boy[] = "cc ...

  8. 深度探索C++对象模型第四章:函数语义学

    C++有三种类型的成员函数:static/nonstatic/virtual 一.成员的各种调用方式 C with class 只支持非静态成员函数(Nonstatic member function ...

  9. 深度探索C++对象模型之第一章:关于对象之关键词所引起的差异

    ————如果不是为了努力维护与C之间的兼容性,C++远比现在简单的多. 如果一个程序员渴望学习C++,但是他却发现书中没有熟悉的struct,一定会苦恼,将这个主题包含到C++里,可以提供语言转移时的 ...

随机推荐

  1. 0620 ALT选择竖排 虚函数的优缺点 浅拷贝深拷贝 操作系统

    1.word按住ALT可以选择整列文字 2.虚函数优点:http://blog.163.com/jianhuali0118@126/blog/static/3774997020083610434091 ...

  2. 条件sql ibatis

    <!-- 多条件查询 --><select id="MS-CUSTOM-PANDECT-INFO-BY-CONDITIONS" resultMap="R ...

  3. 高效IO之File文件操作类的基础用法(二)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 前言 众所周知Java提供File类,让我们对文件进行操作,下面就来简单整理了一 ...

  4. Java中有趣的String、StringBuffer与StringBuilder

    String介绍 String类属于java.lang包中,String类是不可变类,任何对String的改变都会引发新的String对象的生成. 创建String的两种方式: 1.通过构造器创建:S ...

  5. 不在B中的A的子串数量 HDU - 4416 (后缀自动机模板题目)

    题目: 给定一个字符串a,又给定一系列b字符串,求字符串a的子串不在b中出现的个数. 题解: 先将所有的查询串放入后缀自动机(每次将sam.last=1)(算出所有子串个数) 然后将母串放入后缀自动机 ...

  6. css切角效果,折角效果

    html <div class="one">12345</div> <div class="two">abcde</d ...

  7. Linux (raspberry) 安装 telnet server

    可能由于内核或者版本问题 ,网上的telnet服务器安装教程,总是无法安装成功 ,下面说说基于debian发行版(树莓派)telnet 服务器端的安装,便于以后的远程访问. 具体可以通过netstat ...

  8. TOP和PS aux命令显示出来的栏目所代表的意思

    USER: 行程拥有者 PID: pid %CPU: 占用的 CPU 使用率 %MEM: 占用的记忆体使用率 VSZ: 占用的虚拟记忆体大小 RSS: 占用的记忆体大小 TTY: 终端的次要装置号码 ...

  9. 支付宝支付接口-运行支付宝demo

    运行deme 提供了 支付  查询 退款 交易关闭几个简单的接口demo 下载 https://docs.open.alipay.com/270/106291/ 转为mave项目 1.创建一个空的ma ...

  10. Java微服务(Spring-boot+MyBatis+Maven)入门教程

    1,项目创建    新建maven项目,如下图: 选择路径,下一步 输入1和2的内容,点完成 项目创建完毕,结构如下图所示: 填写pom.xml里内容,为了用于打包,3必须选择jar,4和5按图上填写 ...