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

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实例。

 1 //编译器为 class Bar合成一个 default
 2 class Foo { public: Foo(), Foo( int ).. };
 3 class Bar { public: Foo foo;  char*str; };//译注:不是继承,是内含!
 4
 5 void foo bar()
 6 (
 7     Bar bar;    //Bar::foo必须在此处初始化。
 8         //译注:Bar::foo是一个 member object,而其 class Foo
 9         //拥有 default constructor,符合本小节主题
10 if( str )  { }...
11
12 }                  

  被合成的 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 = 0;}

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

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

 1 class Dopey { public : Dopey();...};
 2 class Sneezy {public : Sneezy (int); Sneezy();...};
 3 class Bashful {public : Bashful(); ...};
 4
 5 //class Snow_White内含上述三个类:
 6 class Snow_White{
 7
 8 public:
 9     Dopey dopey;
10     Sneezy sneezy;
11     Bashful bashful;
12 private:
13     int mumble;
14 }
15
16 //如果程序员没有定义默认构造函数,则编译器会自动合成 implicit nontrivial default constructor,其调用顺序按上述声明;
17 //如果程序员定义了如下的构造函数:
18
19 Snow_White::Snow_White() : sneezy(1024)
20 {
21     mumble = 2048;
22 }
23
24 //那么编译器会将上述构造函数自动扩展成如下所示:
25 Snow_White::Snow_White() : sneezy(1024)
26 {
27   //隐式加入member class object 的default  constructors
28    dopey.Dopey::Dopye();
29    sneezy.Sneezy::Sneezy(1024) ;
30    bashful.Bashful::Bashful();
31
32     mumble = 2048;
33 }

原文地址:https://www.cnblogs.com/ccpang/p/11365967.html

时间: 2024-10-10 23:34:08

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

【深度探索C++对象模型】第二章 构造函数语意学(上)

第二章 构造函数语意学(The Semantics of Constructors) -- 本书作者:Stanley B.Lippman 一.前言 首先让我们来梳理一个概念: 默认构造函数(Default Constructor) : 是在没有显示提供初始化式时调用的构造函数.它由不带任何参数的构造函数,或是为所有形参提供默认实参的构造函数定义.如果定义的某个类的成员变量没有提供显示的初始化式时,就会调用默认构造函数(Default Contructor). 如果用户的类里面,没有显示的定义任何

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

默认构造函数的构建操作 默认构造函数在需要的时候被编译器合成出来.这里"在需要的时候"指的是编译器需要的时候. 带有默认构造函数的成员对象 如果一个类没有任何构造函数,但是它包含一个成员对象,该成员对象拥有默认构造函数,那么这个类的隐式默认构造函数就是非平凡的,编译器需要为该类合成默认构造函数.为了避免合成出多个默认构造函数,编译器会把合成的默认构造函数.拷贝构造函数.析构函数和赋值拷贝操作符都以内联的方式完成.一个内联含有具有静态链接,不会被文件以外者看到.如果函数不适合做成内联,就

深度探索C++对象模型 第三章 Data 语意学

一个有趣的问题:下列 类 sizeof大小 class X{}    //1 class Y:public virtual X{} //4 or 8 class Z:public virtual X{} // 4 or 8 class A:public Y,public Z{} // 8 or 12 主要原因:为了保持每一个类生成对象在内存中的唯一性,编译器必须要给空类生成一个char来维持object的唯一性: 而virtual继承中,仅保持了base class的指针,有些编译器会继承bas

深度探索C++对象模型第6章 执行期语意学

(一)对象的构造和析构(Object Construction and Destruction) 一般而言我们会把object尽可能放置在使用它的那个程序区段附近,这么做可以节省非必要的对象产生操作和摧毁操作. 全局对象 如果我们有以下程序片段: Matrix identity main() { //identity 必须在此处被初始化 Matrix m1=identity; ... return 0; } C++保证,一定会在main()函数中第一次用到identity之前,把identity

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

在使用C++时,常常会好奇或者抱怨,编译器为我们做了什么事呢? 为什么构造函数没有为我初始化呢?为什么我还要写默认构造函数呢? 2.1 Default Constructor 的构造操作 如果没有声明默认构造函数,编译器会在需要的时候帮我们产生出来. 为了避免在多个地方被需要导致重复,则编译器将产生的构造函数声明为inline方式. class Foo {public:Foo(), Foo(int) }; class Bar {public: Foo foo;char *str;} Bar ba

【深度探索C++对象模型】第一章 关于对象

第一章 关于对象(Object Lessons) -- 本书作者:Stanley B.Lippman 一.前言 什么是 C++ 对象模型:简单的说,就是 C++ 中面向对象的底层实现机制. 本书组织: 第 1 章,关于对象(Object Lessons),介绍 C++ 对象的基础概念,给读者一个粗略的了解. 第 2 章,构造函数语意学(The Semantics of Constructors),构造函数什么时候会被编译器合成?它给我们的程序效率带来了怎样的影响? 第 3 章,Data语意学(T

深度探索C++对象模型 第五章 构造、析构、拷贝语意学

1. const 成员函数需要吗? 尽量不要,如果存在继承,则无法预支子类是否有可能改变data member 2. pure virtual constructor 可以实现类的隐藏吗(包含data member)?   这样子类无法调用base 的构造函数对数据初始化,所以可以用protected来实现构造函数,可以实现子类调用: 3. 如果class中存在virtual function,则编译器会再构造函数中对vptr进行初始化(在base构造函数调用之后,而代码实现之前) 4.拷贝构造

《深度探索C++对象模型》之《构造函数语义学》章节探索与理解

构造函数语义学 这一章原书主要分析了:编译器关于对象构造过程的干涉,即在对象构造这个过程中,编译器到底在背后做了什么 这一章的重点在于default constructor和copy constructor的相关知识 Default Constructor 一些C++的书籍中告诉我们:如果不写任何构造函数,那么编译器会为我们生成一个默认构造函数(default constructor) 那么,针对这个观点,有两个问题: 这句话是正确的吗? 如果这句话是正确的,那么编译器为我们生成的default

Android深度探索(卷一)第二章读书笔记

第二章讲述了搭建Android开发环境.我们首先需要知道Android底层开发需要哪些工具.开发.测试和调试Linux驱动.HAL程序库需要的工具有:JDK6或以上版本.Eclipse3.4或以上版本.ADT.CDT.Android SDK.Android NDK.交叉编译环境.Linux内核源代码.Android源代码.minicom. 在明确开发工具后,我们就要进行开发环境的搭建. 安装JDK.方法一:通过官网下载JDK,并进行配置.配置需要在profile文件来设置PATH环境变量.过程为