对象初始化以及在构造函数和析构函数中调用virtual函数的问题

新家中的第一篇文章,开始往往是比较难的,这篇文章也是酝酿了好久了,开始的时候觉得没有那么必要把这点儿事儿说出来,或许大家都很懂了,我只是班门弄斧而已。但是鉴于自己也可能在未来的某个时候把这个事儿忘了,所以还是记录一下吧!知道的就跳过吧~~

 

说下有关C和C++的个人看法吧。记得大三的时候上过一门儿《C++高级编程》(老师陈刚,此人讲课深入浅出,很是牛B)。当时老师在课程的最后给出了一个作业,要我们说出C和C++的区别。那个时候因为学的太差,也不甚理解,所以基本也就是套话。工作了,有幸在这两年从C的项目做到C++的项目,觉得有了一些理解、有了一些想法。概括的说吧,我觉得C++是一门远比C复杂的语言。对了,就这么简单!这其中分成几个方面吧。第一,语言本身的复杂!C其实是一门儿非常简单的语言,linux的大门儿对C大敞着!而加上了模版、类、继承之后,C++变得异常的臃肿。可能是为了兼容C语言吧,也可能是其他原因,C++语言没有规定很多编译器实现、以及语言本身的细节,所以增加了若干的未定义情节。而且在编译器的介入下,构造函数、拷贝构造函数、赋值运算、析构函数会产生很多意想不到的状况。这就是很多码农大呼狗日的编译器的原因!(以后会有专门的文说这个事儿)。第二,在模版和类的推动下,C++语言有着远比丰富的第三方库,具体的不说了大家都懂的(真相是我也不知道几个,只知道多)。

 

Effective C++笔记

条款4:确定对象被使用前已被初始化

整个这个条款看下来,我觉得作者的意思是在强调必须给每个类成员初值(0),特别是内置类型类成员。

这其中其实有一个比较简单的规则:如果是全局或者静态变量(包括内置类型或类类型)所分配的内存区域将会被初始化为0,那么类类型中的类成员初值就算不处理也会是0。如果变量(包括内置类型或类类型)在栈或者堆上面声明的,那么编译器将不会保证所分配的内存初值是0,这就是一种未定义状态,我们不知道未处理的类成员值到底是啥。我们要做的就是在构造函数里面一定给每个类成员初值。

另外,我觉得作者在简化的过程中,漏掉了一个叫做POD的东西。这个东西很有意思,不过要一点儿一点儿的说。问一个问题:在未定义类构造函数的时候,编译器一定会给它合成一个构造函数么?答案是:不一定。C++标准貌似是这样说的:C++在需要的时候会在用户未定义类构造函数的时候为类合成一个默认构造函数。注意哦,是在编译器认为需要的时候!不是用户需要的时候!!而C++为了兼用C语言,有个特殊的东西,这就是Plain Old Data——POD。具体解释在这个位置有详细说明http://www.fnal.gov/docs/working-groups/fpcltf/Pkg/ISOcxx/doc/POD.html。简单的说,就是只含有内置数据类型成员的类。如下面这个:

1 class Point3d{
2 public:
3   double x, y, z;
4 };

而就像这样的类,编译器是不会合成默认构造函数的!C++规定类成员会在默认构造函数执行之前定义(初始化)。所以,这样的类成员是未初始化的!更别说给初值了!在如下的代码中,会报类成员未初始化的错误!(VS2010中正常报出来,G++中说啥都没出来,据说valgrind可以检测出来)

1 int main(){
2   Point3d p;//未调用自动合成的默认构造函数,因为根本就没有默认构造函数被合成出来
3   p.x = 1.0;
4   return 0;
5 };

所以,这种情况就不是没给初值那么简单了,这种情况下类成员就压根儿没被初始化。不过如果遵守每个类都要自己定义构造函数,并给类成员初值,那么这种情况也就避免了。虽然已经避免了,不过还是要提一下,这是一个特殊情况。

 

条款9: 绝不在构造和析构函数中调用virtual函数

这个条款,我同样觉得作者是为了最大化的保证程序的正确执行,正如作者所说,不在构造函数和析构函数中调用virtual函数是最安全的做法。但是从语言的角度上看这个问题的结果是,并不是完全不可以调用virtual函数,合理的使用是不会出问题的!那么在构造函数和析构函数中调用virtual函数的背后到底发生了什么事情呢?

我们设定一个情况吧:A是base class并定义了一些纯虚函数func1和func2,B继承了A并实现了func1,C继承了B并实现了func1和func2。

情况一:如果在B的构造函数中调用func1,那么它调用到的只能是B中定义的版本,而不是C中定义的版本。

情况二:如果B的构造函数中调用func2,会报错!

为什么会这样呢?根本原因是:在定义一个C类对象的时候,它会首先调用A的构造函数,接着调用B的构造函数,最后调用C的构造函数。在切换构造函数调用的过程中,编译器会修改this指针(发生在多重继承中)和vptr。比如在B构造函数调用的时候,vptr指向的是B类的虚函数表,所以此时通过this指针使用虚函数func1的时候,它调用的只是B类中的版本哦!当然,typeinfo也会是B类的。析构函数也是类似的情况,在切换调用析构函数的过程中this函数和vptr也会进行切换的。

另外,这是不含有virtual继承的情况,如果存在virtual继承,那么情况会更复杂!

这才是问题的根本原因哦~~当然了,在构造函数和析构函数中不调用virtual函数是最最稳妥的!

posted on 2012-09-17 22:51  William.Wu  阅读(683)  评论(0编辑  收藏  举报

导航