默认构造函数可以初始化基本类型的成员变量,但是自定义的类型就不能正确初始化了。在这种情况下,需要我们自定义构造函数。但是,如果我们缺少默认的构造函数,会造成后续使用该类的时候会有一些限制。
1. 有些类的数据成员的值是必要的,因而在创建时就应该提供其值,其中的一个强制实现的方法就是不提供默认构造函数。如果不这么做的话,在使用对象的时候就要检验那个数据成员是否被正确赋值。
2. 如果为了强制某些数据成员在创建对象时就被赋值而不提供默认构造函数,那么在创建该对象的数组时就会遭遇因为没有默认构造函数而无法创建的情况,主要的解决方法有以下三种:
例如:
class A{ public : A(int IDNumver);...};
在定义A bP[10];会失败,因为无法调用其默认构造函数。
1)在堆栈中创建数组,并使用显示初始化列表。如:
A bP[4]={A(1),A(2),A(3),A(4)};
缺点:比较麻烦,如果有数组大小为1000,那么初始化列表就要有写1000个构造函数,显然不可能;
只适用于堆栈数组,堆中的数组没有此语法
2)使用指针数组(指针不受构造函数的束缚)。如:
A * ptr_A[10];//不用调用构造函数
A **ptr_A=new (A*)[10];//也可
缺点:如果指针指向的是堆中内存,则要记得delete,将此数组所指的所有对象删除,否则会造成内存泄露(加重了程序员负担);
存放指针需要额外空间,还需空间存放类对象
3)使用内存池:使用operator new[] 函数预先申请一块内存,要使用的时候再使用placement new(定位new)依次构造,如:
void* rawMemory=operator new[](10*sizeof(A));
A *a=static_cast<A*>(rawMemory);
for(int cou=0;cou<10;++cou)
new (&a[i]) A(1);
优点:可以在堆中创建数组而且不需占用额外空间
缺点:1)在数组内的对象结束生命时需要手动调用其析构函数,最后调用operator delete[]释放内存
for(int i=9;i>=0;--i)
a[i].~A();
operator delete[](rawMemory);
2)如果采用一般的数组删除语法,结果将不可预期,因为删除一个不是new operator 获得的指针,结果没有定义。
如:delete [] a; //没有定义,因为a并非来自new操作符
故只能采用1)的方法
3)关于new operator和placement new 以及operator new的区别见条款8.
3. 如果该类没有默认构造函数,那么它有可能不适用有些模板,当然这里的“有些模板"主要指程序员自己定义的模板,对于标准库中的容器模板大多采用了2所述的内存池的方法,这也是使用某个类实例化标准库容器的必要条件只是该类能被复制而并不要求该类有默认构造函数的原因。也就是说,如果类的设计者自定义的类模板(准确的说应该是容器模板)使用内存池的方法的话,这一个缺陷可以避免。
4. 总结:
尽管不提供默认构造函数对于构造数组会造成一定麻烦,但对于如果仅仅为了方便构建数组而提供一个默认构造函数,在使用某个对象时就需要检查相关的数据成员是否被合理赋值,"调用者便必须为测试行为付出时间代价“,而且提供一个无意义的默认构造函数也会影响类的效率(毕竟默认构造函数的出现只是为了方便构建数组,其实什么也不做),所以如果一个default constructor无法提供所有数据成员都被正确初始化,那么不对其进行定义,尽管可能对类的使用带来限制(如上文所提到的数组的构建),但也保证了使用对象时其必要的数据成员已被合理初始化。