关于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. Java基础知识强化之IO流笔记31:转换流出现的原因和格式

    1. 由于字节流操作中文不是特别方便,所以Java就提供了转换流.  字符流 = 字节流 + 编码表 2. 编码表 由字符及其对应数值组成的一张表 常见的编码表: • ASCII/Unicode字符集 ...

  2. Google Map API v2 (三)----- 地图上添加标记(Marker),标记info窗口,即指定经纬度获取地址字符串

    接上篇 http://www.cnblogs.com/inkheart0124/p/3536322.html 1,在地图上打个标记 private MarkerOptions mMarkOption; ...

  3. Eclipse下安装/配置Jrebel6.X

    Eclipse3.6+下安装/配置Jrebel6.X 1. 为什么要使用Jrebel 在日常开发过程中, 一旦修改配置/在类中增加静态变量/增加方法/修改方法名等情况, tomcat不会自动加载, 需 ...

  4. Python获取web页面信息

    import sys, urllib2 # req = urllib2.Request(sys.argv[1]) req = urllib2.Request('http://www.sina.com. ...

  5. js--小结⑥---typeof

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. CI 笔记7,easyui 异步加载

    在做后台导航时,需要异步加载,pid和id的循环问题,在controller中,建立另外一个方法,嵌套循环,查找是否pid〉1. public function nav_list() { $this- ...

  7. OC基础-day06

    #pragma mark - Day06_01_点语法 1. 点语法. 1). 如果要访问对象的属性,还要去调用属性对应的setter getter方法.好烦躁好烦躁. 2). 点语法的作用: 快速调 ...

  8. C#中如何正确的操作字符串?

    字符串应该是所有编程语言中使用最频繁的一种基础数据类型.如果使用不慎,我们就会为一次字符串的操作所带来的额外性能开销而付出代价.本条建议将从两个方面来探讨如何规避这类性能开销: 1. 确保尽量少的装箱 ...

  9. html5 + css3 + zepto.js实现的微信广告宣传页

    最新学习html5 + css3, 参考微信的一个推广页写出一个实例巩固自己知识,自己已经将原实例打包到自己博客文件当中,但是不知道如何提供下载,如有需要的朋友可以联系我qq309666726

  10. CSS图片去色

    .imgFilter { filter: grayscale(100%); -webkit-filter: grayscale(100%); -moz-filter: grayscale(100%); ...