精品文章 论C++构造函数中的不合理设计(1)(4)
5、创建一个缺省构造函数
当继承被使用的时候,却省构造函数就会被调用。更明确地说,当继承层次的最晚层的类被构造的时候,所有基类的构造函数都在派生基类之前被调用,举个例子来说,看下面的代码:
- #include<iostream.h>
- class Base
- {
- int x;
- public :
- Base() : x(0) { } // The NULL constructor
- Base(int a) : x(a) { }
- };
- class alpha : virtual public Base
- {
- int y;
- public :
- alpha(int a) : Base(a), y(2) { }
- };
- class beta : virtual public Base
- {
- int z;
- public :
- beta(int a) : Base(a), z(3) { }
- };
- class gamma : public alpha, public beta
- {
- int w;
- public :
- gamma ( int a, int b) : alpha(a), beta(b), w(4) { }
- };
- main()
- {.....
- }
在这个例子中,我们没有在gamma的头文件中提供任何的初始化函数。编译器会为基类使用缺省的构造函数。但是因为你提供了一个构造函数,编译器就不会提供任何缺省构造函数。正如你看到的这段包含缺省构造函数的代码一样,如果删除其中的缺省构造函数,编译就无法通过。
如果基类的构造函数中引入一些副效应的话,比如说打开文件或者申请内存,这样程序员就得确保中间基类没有初始化虚基类。也就是,只有虚基类的构造函数可以被调用。
虚基类的却省构造函数完成一些不需要任何依赖于派生类的参数的初始化。你加入一个init()函数,然后再从虚基类的其他函数中调用它,或在其他类中的构造函数里调用(你的确保它只调用了一次)。
6、不能取得构造函数的地址
C++中,不能把构造函数当作函数指针来进行传递,指向构造函数的的指针也不可以直接传递。允许这些就可以通过调用指针来创建对象。一种达到这种目的的方法是借助于一个创建并返回新对象的静态函数。指向这样的函数的指针用于新对象需要的地方。下面是一个例子:
- class A
- {
- public:
- A( ); // cannot take the address of this
- // constructor directly
- static A* createA();
- // This function creates a new A object
- // on the heap and returns a pointer to it.
- // A pointer to this function can be passed
- // in lieu of a pointer to the constructor.
- };
这一方法设计简单,只需要将抽象类置入头文件即可。这给new留下了一个问题,因为准确的类型必须是可见的。上面的静态函数可以用来包装隐藏子类。
7、位拷贝在动态申请内存的类中不可行
C++中,如果没有提供一个拷贝构造函数,编译器会自动生成一个。生成的这个拷贝构造函数对对象的实例进行位拷贝。这对没有指针成员的类来说没什么,但是,对用了动态申请的类就不是这样的了。为了澄清这一点,设想一个对象以值传递的方式传入一个函数,或者从函数中返回,对象是以为拷贝的方式复制。这种位拷贝对含有指向其他对象指针的类是没有作用的。当一个含有指针的类以值传递的方式传入函数的时候,对象被复制,包括指针的地址,还有,新的对象的作用域是这个函数。在函数结束的时候,很不幸,析构函数要破坏这个对象。因此,对象的指针被删除了。这导致原来的对象的指针指向一块空的内存区域-一个错误。在函数返回的时候,也有类似的情况发生。
这个问题可以简单的通过在类中定义一个含有内存申请的拷贝构造函数来解决,这种靠叫做深拷贝,是在堆中分配内存给各个对象的。
8、编译器可以隐式指定强制构造函数
因为编译器可以隐式选择强制构造函数,你就失去了调用函数的选择权。如果需要控制的话,不要声明只有一个参数的构造函数,取而代之,定义helper函数来负责转换,如下面的例子:
- #include <stdio.h>
- #include <stdlib.h>
- class Money
- {
- public:
- Money();
- // Define conversion functions that can only be
- // called explicitly.
- static Money Convert( char * ch )
- { return Money( ch ); }
- static Money Convert( double d )
- { return Money( d ); }
- void Print() { printf( "%f", _amount ); }
- private:
- Money( char *ch ) { _amount = atof( ch ); }
- Money( double d ) { _amount = d; }
- double _amount;
- };
- void main()
- {
- // Perform a conversion from type char *
- // to type Money.
- Money Account = Money::Convert( "57.29" );
- Account.Print();
- // Perform a conversion from type double to type
- // Money.
- Account = Money::Convert( 33.29 );
- Account.Print();
- }
在上面的代码中,强制构造函数定义为private而不可以被用来做类型转换。然而,它可以被显式的调用。因为转换函数是静态的,他们可以不用引用任何一个对象来完成调用。
总结
要澄清一点是,这里提到的都是我们所熟知的ANSI C++能够接受的。许多编译器都对ANSI C++进行了自己的语法修订。这些可能根据编译器的不同而不同。很明显,许多编译器不能很好的处理这几点。探索这几点的缘故是引起编译构造的注意,也是在C++标准化的过程中移除一些瑕疵。