4.避免无意义的默认构造函数

前言

 
所谓默认构造函数,指的是无需任何信息即可构造对象。有些对象需要它,诸如容器默认构造为空之类。但也有很多对象不需要它,比如工厂里每一个设备对应一个id,不存在没有id就能创建的设备。


缺乏默认构造函数所带来的限制

 
一般来说,如果一个类不存在默认构造函数,那么它的使用会有诸多限制。举例而言,现有一个class表示公司内部设备,其对象必须通过id才能生成:

1
2
3
4
5
class EquipmentPiece {
public:
EquipmentPiece(int IDNumber);
...
};

因为EquipmentPiece类没有默认构造函数,在以下三种情况下使用它会出现问题:

数组初始化

我们无法构建一个由Equipment对象构成的数组,因为在产生数组时无法为数组中的对象指定构造参数:

1
2
EquipmentPiece bestPieces[10]; //error
EquipmentPiece *bestPieces = new EquipmentPiece[10];//error

以下给出解决方案。


使用non-heap数组(不在堆中给数组分配内存)

1
2
3
4
5
6
7
8
9
int ID1, ID2, ID3, ..., ID10; 
...
EquipmentPiece bestPieces[] = {
EquipmentPiece(ID1),
EquipmentPiece(ID2),
EquipmentPiece(ID3),
...,
EquipmentPiece(ID10)
};

不过此法无法延伸至heap数组。

使用指针数组而非对象数组

1
2
3
4
typedef EquipmentPiece* PEP; 
PEP *bestPieces = new PEP[10];
for (int i = 0; i < 10; ++i)
bestPieces[i] = new EquipmentPiece(ID Number);

不过该方案有两个缺点:

  1. 删除数组前必须现删除数组里每个指针所指向的对象,否则内存泄漏。
  2. 增加了内存分配量,比一般性数组多存储了指针。

这两个缺点均可解决,针对第一条,我们可以使用RAII避免内存泄漏。针对第二条,我们可以使用placement new操作完成构造(Effecive C++ 53)。具体如下:

1
2
3
4
void *rawMemory = operator new[](10*sizeof(EquipmentPiece));
EquipmentPiece *bestPieces = static_cast<EquipmentPiece*>(rawMemory);
for (int i = 0; i < 10; ++i)
new(&bestPieces[i]) EquipmentPiece(ID Number);//placement new 在指定位置构造对象


不适用于template-based container classes

对于那些基于模板的容器类而言,被实例化的目标类型必须要有一个默认构造函数,因为那些template中几乎总会有一个以template类型参数作为元素类型的数组,举例而言:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T>
class Array {
public:
Array(int size);
...
private:
T *data;
};
template<class T>
Array<T>::Array(int size){
data = new T[size]; // 为每个数组元素依次调用 T::T()
...
}

该问题可以通过良好的模板设计习惯来加以避免。


virtual base class

不提供缺省构造函数的虚基类,使用起来及其低效。因为几乎所有的派生类在实例化时都必须给虚基类构造函数提供参数,这就要求所有由没有缺省构造函数的虚基类继承下来的派生类(无论有多远)都必须知道并理解提供给虚基类构造函数的参数的含义。


总结

 
如果构造函数可以确保对象的所有字段正确初始化,默认构造函数就可以不必出现。