关于c++,最常听到的一个抱怨是,编译器背着程序员做了太多事情,conversion运算符是最常被引用的一个例子:jerry schwarz,iostream函数库的建筑师,就曾经说过一个故事,他说他最早的意图是支持一个iostream class object的纯量测算(scalar test),类似这样:

if(cin) ...

为了让cin能够求得一个真假值,jerry首先为它定义了一个conversion运算符operator int(),运行良好,但是在下面的这种错误的程序设计中,它的行为就势必令人大吃一惊了:

//wow,应该是cout,而不是cin

cin<<intVal;

Class层次结构的“type-safe”填写应该能够捕捉这类输出运算符的错误运用,然而,带有几分唯物主义色彩的编译器,比较喜欢找到一个正确的诠释(如果有的话)。而不只是把程序表示为错误就可以了。在上例中,内建的左移运算符《《,只有在“cin改变为和一个整数值同义”时才适用,编译器会检查可以使用的各个conversion运算符,然后找到了operator int(),ok,当做左移。

int tmp=cin.operator int();

tmp<<intVal;

Jerry如何解决这个意想不到的行为,他以operator void*()取代了operator int().这种错误有时被戏称为“Schwarz Error”。

(if (cin)会调用operator void*();

operator void*是istream的转换函数,if要求操作数具有标量类型,而void*就是标量类型,它是最佳匹配函数,于是就调用它进行转换了。 If the stream is valid, the result of the conversion is a non-null pointer. The result is the null pointer if the stream isn't valid.(参考:http://www.parashift.com/c++-faq/istream-and-while.html)

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

void foo_bar()

{

//wow,程序要求bar's member都被清0

Foo bar;

if(bar.val || bar.pnext) //do something

}

(vs运行报错,bar is used without being initilaized)

在这个例子中,正确的程序语义是要求Foo有一个defautl constructor,可以将他的2个members初始化为0,然后,编译器合成的构造函数不会将成员清0,为了让上一段代码正确运行,必须提供一个明显的default constructor,将2个member适当地初始化。

(个人注解:

如果不要Foo foo这种形式,采用new来

Foo *foo=new Foo();
cout<<foo->val<<ends<<foo->pnext<<endl;

可以发现,输出:0。使用new是初始化了类的数据成员了的。

new过程:

1. 调用operator new分配内存 ;
2. 调用构造函数生成类对象;
3. 返回相应指针。

通常很多C++程序员存在两种误解:

  • 没有定义默认构造函数的类都会被编译器生成一个默认构造函数。
  • 编译器生成的默认构造函数会明确初始化类中每一个数据成员。

C++标准规定:如果类的设计者并未为类定义任何构造函数,那么会有一个默认 构造函数被暗中生成,而这个暗中生成的默认构造函数通常是不做什么事的(无 用的),trivial下面四种情况除外。

换句话说,有以下四种情况编译器必须为未声明构造函数的类生成一个会做点事 的默认构造函数。(nontrivial constructor)我们会看到这些默认构造函数仅“忠于编译器”,而可能不会按 照程序员的意愿程效命。

1.包含有带默认构造函数的对象成员的类(带有default constructor的member class object)

如果一个class没有任何constructor,但他含有一个member object,而后者有defautl constructor,那么这个class的implicit

defautl constructor就是“nontrivial”,编译器需要为此class合成出一个default constructor,不过这个合成操作只有在constructor真正需要被调用时发生。

classFoo {publicFoo(),Foo(int);...};

classBar {public: Foofoo; charstr;};

void foo_bar(){

Bar bar; //Bar::foo应在此处被初始化

if(str){...}

}

被合成的Bar default ctor内含必要的代码,能够调用class Foo的default ctor来处理member object Bar::foo,但并不处理Bar::str。即被合成的default ctor只是为了满足编译器的需要(编译器需要有个地方来初始化Bar::Foo,因为它有自己的default ctor),而不是程序的需要(初始化Bar::str是程序员的动作)。

如果程序员在ctor只显式初始化了Bar::str,则一些代码会被插入到这个ctor中。例如

Bar::Bar(){ str=0;      }

会被编译器扩展为

Bar::Bar(){

foo.Foo::Foo(); //附加上的编译器代码

str=0;//显式的程序员代码

}

(虽然default constructor已经被明确定义,编译器没办法合成第二个,但是我们需要初始化member object foo,怎么办?

编译器的行动是“如果class A内含一个或以上的member class objects,那么classA的每一个constructor必须调用每一个member classs的defautl constructor,编译器会扩张已存在的constructor。在其中安插一些代码。使得user code在被执行之前,先调用必要的default constructor,)

如果有多个class member object都要求constructor的初始化操作,将如何呢?c++要求以“member objects在class中的声明次序”来调用各个constructor。这一点有编译器完成。

2.带有defautl constructor“的base class。

如果一个没有定义任何构造函数的类派生自带有默认构造函数的基类,那么编译 器为它定义的默认构造函数,将按照声明顺序为之依次调用其基类的默认构造函 数。若该类没有定义默认构造函数而定义了多个其他构造函数,那么编译器扩充 它的所有构造函数——加入必要的基类默认构造函数。另外,编译器会将基类的默 认构造函数代码加在对象成员的默认构造函数代码之前。

3.带有一个virtual function的class

另有2中情况,也需要合成出defautl constructor。

1.     这个类自己声明(或者继承)了Virtual Function。

2.     这个类继承自一个继承串链,其中有一个或多个virtual base class。

不管哪一种情况,由于缺乏有user声明的constructor,编译器会详细记录合成一个defautl constructor的必要信息,以下面的程序片段为例;

class Widget{

public:

virtual void flip()=0;

//...

};

void flip(const Wideget& widget) { widget.fiip();}

//假设Bell和whistle都派生子widget

void foo()

{

Bell b;

Whistle w;

flip(b);

flip(w);

}

这种情况下,编译时,要做如下工作:

1.编译器需要生成一个virtual function table(vtbl)并填充。

2.在class object中,一个额外的pointer member(就是vptr,指向vtbl)会被编译器合成出来。此外虚拟调用会被替换(w.vf() => w.vprt[1])。

为了支持这种功能,编译器必须为每个w对象设置它的vptr(这是成员变量,此时需要指向合适的vtbl),因此编译器需要在default ctor中安插一些代码来完成这种工作。

4.带有一个virtaul Base class的class

virtual base class的实现法在不同编译器之间有极大的差异,然而,每一种实现法的共同点在于必须使virtaul base classs在其每一个derived class object中的位置,能够于执行期准备妥当,例如下面这段程序代码中:

考虑这样的代码:

classX { publicinti; };

classA : publicvirtualX   { publicin tj; };

classB : publicvirtualX   { publicdouble d; };

classC : publicA, publicB { publicint k; };

//无法在编译期间解析出 pa->X::i 的位置(给一个pa无法确定i的地址)。

void fooconstA* pa ) { pa->i = 1024; }

main() {

foo( new A );

foo( new C );

// ...

}

由于pa的真正类型不确定,所以某些编译器会记录一个指针例(如 __vbcX)来记录X,然后通过这个指针来定位pa指向的i。上述

void fooconstA* pa ) { pa->i = 1024; }

变成了:

void fooconstA* pa ) { pa-> __vbcX ->i = 1024; }

因此,__vbcX这个指针(执行virtual base class)需要在object构造期间设置好。于是编译器需要一个default ctor来完成这个工作。

《深度探索c++对象模型》chapter2 构造函数语义学的更多相关文章

  1. 【深度探索C++对象模型】data语义学

    class X{}; class Y :public virtual X{}; class Z :public virtual X{}; class A :public Y, public Z{}; ...

  2. 【深度探索c++对象模型】Function语义学之虚函数

    虚函数的一般实现模型:每一个class有一个virtual table,内含该class中的virtual function的地址,然后每个object有一个vptr,指向virtual table. ...

  3. 【深度探索c++对象模型】Function语义学之成员函数调用方式

    非静态成员函数 c++的设计准则之一就是:非静态成员函数至少和一般的非成员函数有相同的效率.编译器内部已将member函数实体转换为对等的nonmember函数实体. 转化步骤: 1.改写函数原型以安 ...

  4. 《深度探索C++对象模型》读书笔记(一)

    前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...

  5. [读书系列] 深度探索C++对象模型 初读

    2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...

  6. 深度探索C++对象模型

    深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...

  7. c++学习书籍推荐《深度探索C++对象模型》下载

    百度云及其他网盘下载地址:点我 百度云及其他网盘下载地址:点我 编辑推荐 如果你是一位C++程序员,渴望对于底层知识获得一个完整的了解,那么这本<深度探索C++对象模型>正适合你 作者简介 ...

  8. 读书笔记《深度探索c++对象模型》 概述

    <深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...

  9. 柔性数组-读《深度探索C++对象模型》有感 (转载)

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

  10. 柔性数组-读《深度探索C++对象模型》有感

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

随机推荐

  1. Base64算法

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/SJQ. http://www.cnblogs.com/shijiaqi1066/p/4288372.html ...

  2. div随另一个div自动增高

    <script type="text/javascript"> document.getElementById("div1").style.heig ...

  3. PHPStorm&PHPstudy环境配置

    因为实习要求,最近在学php,补下开发环境的配置,原博客链接:点击打开链接 1.创建新的项目(project),创建完成之后单击工具栏的应用运行/调试(Select Run/Debug Configu ...

  4. 【锋利的jQuery】学习笔记02

    第二章 jQuery选择器 一.jQuery选择器的优势 写法简洁 $("div") 支持css2和css3选择器(对于css3选择器支持这一项,我认为应该是jQuery首先创造并 ...

  5. ios专题 - 使用bundle文件管理资源

    [原创]http://www.cnblogs.com/luoguoqiang1985 以前,自己写程序,图片等资源放得比较乱.后来,发现有个更好的方法来管理图片等资源文件 --bundle文件. 1) ...

  6. jax-ws实现WebService

    关于WebService有很多框架了,CXF,Spring自己的webservice等等,因为cxf实际也是依赖spring的servlet,这里说明一下jax-ws,使用原生的servlet实现. ...

  7. PAT_1008 数组元素循环右移问题

    题目描述: 一个数组A中存有N(N>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(M>=0)个位置,即将A中的数据由(A0A1……AN-1)变换为(AN-M …… AN ...

  8. HDU 题目分类

    转载自新浪博客,, http://blog.sina.com.cn/s/blog_71ded6bf0100tuya.html 基础题: 1000.1001.1004.1005.1008.1012.10 ...

  9. ul ol dl

    1.ul是无序列表,也就是说没有排列限制可以随意加li: <ul> <li>可以随意放置</li> <li>可以随意放置</li> < ...

  10. 【python】【转】Python生成随机数的方法

    如果你对在Python生成随机数与random模块中最常用的几个函数的关系与不懂之处,下面的文章就是对Python生成随机数与random模块中最常用的几个函数的关系,希望你会有所收获,以下就是这篇文 ...