关于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. ionic 项目分享【转】No.3

    写在文章前:由于最近研究ionic框架,深感这块的Demo寥寥可数,而大家又都藏私,堂堂天朝,何时才有百家争鸣之象,开源精神吾辈当仁不让! 原文地址暂时忘记了 ,如果有知道的麻烦在评论处帮忙说一下 , ...

  2. (转)C# NameValueCollection集合

    1.NameValueCollection类集合是基于 NameObjectCollectionBase 类. 但与 NameObjectCollectionBase 不同,该类在一个键下存储多个字符 ...

  3. Andriod ADT v22.6.2版本中在Mainactivity.java中使用fragment_main.xml中TextView控件对象的问题

    众所周知,我们既可以在 activity_main.xml文件中控制activity中的view,也可以使用java代码的set..()方法控制它.在学习过程中,发现在ADT新版本中,和以前版本有区别 ...

  4. Apache Shiro 快速入门教程,shiro 基础教程 (这篇文章非常好)

    第一部分 什么是Apache Shiro     1.什么是 apache shiro :   Apache Shiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理 ...

  5. ios 用LLDB查看模拟器文件路径以及一些常用的命令

    我看网络上有好多有关lldb调试命令的介绍,我都看了一遍,都没有这个方法,所以我在这里补充出来,帮助需要的人. 另外附上一些 实用LLDB命令 我们可以使用e命令定义变量 (lldb) e NSStr ...

  6. ZOJ 2432 Greatest Common Increasing Subsequence(最长公共上升子序列+路径打印)

    Greatest Common Increasing Subsequence 题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problem ...

  7. a-b(高精度)

    我现在已经是才语言中的一员了,我在此献上今日的佳作——a-b(高精度),以下是我的程序及其注释,欢迎各位来观赏,耶! 程序: #include<stdio.h> #include<s ...

  8. LVS 介绍以及配置应用

    1.负载均衡集群介绍 1.1.什么是负载均衡集群 负载均衡集群提供了一种廉价.有效.透明的方法,来扩展网络设备和服务器的负载.带宽.增加吞吐量.加强网络数据的处理能力.提高网络的灵活性和可用性 搭建负 ...

  9. linux安装git方法(转)

    转自:http://jingyan.baidu.com/article/e9fb46e16698687521f766ec.html 以下内容亲测,确实可行. 由于我的机器是linux6.7,所以省略了 ...

  10. 《ln命令》-linux命令五分钟系列之十八

    本原创文章属于<Linux大棚>博客,博客地址为http://roclinux.cn.文章作者为rocrocket. 为了防止某些网站的恶性转载,特在每篇文章前加入此信息,还望读者体谅. ...