More Effective C++ 04:避免无用的默认构造函数

在很多时候,对于很多对象来说,不利用外部数据进行完全的初始化是不合理的。比如一个没有输入姓名的地址簿对象,就没有任何意义。
在一些公司里, 所有的设备都必须标有一个公司ID号码,所以在建立对象以模型化一个设备时,不提供一个合适的ID号码,所建立的对象就根本没有意义。
考虑以下的类:

class EquipmentPiece { 
public: 
 EquipmentPiece(int IDNumber); 
 ... 
};

因为EquipmentPiece 类没有一个默认构造函数,所以在三种情况下使用它,就会遇到问题。
第一种情况是建立数组时。一般来说,没有一种办法能在建立对象数组时给构造函数传递参数。所以在通常情况下,不可能建立 EquipmentPiece 对象数组:
在这里插入图片描述
在这里插入图片描述
不过还是有三种方法能回避开这个限制。
对于使用非堆数组(即不在堆中给数组分配内存)的一种解决方法是在数组定义时提供必要的参数:

在这里插入图片描述
不过很遗憾,这种方法不能用在堆数组的定义上。 一个更通用的解决方法是利用指针数组来代替一个对象数组:
在这里插入图片描述
不过这种方法有两个缺点:
第一你必须删除数组里每个指针所指向的对象。如果你忘了,就会发生内存泄漏。
第二增加了内存分配量,因为正如你需要空间来容纳 EquipmentPiece对象一样,你也需要空间来容纳指针

如果你为数组分配原始内存,你就可以避免浪费内存。使用 placement new 方法在内存中构造 EquipmentPiece 对象:

// 为大小为 10 的数组分配足够的内存 
// EquipmentPiece 对象; 
// operator new[] 函数 
void *rawMemory = operator new[](10*sizeof(EquipmentPiece)); 
EquipmentPiece *bestPieces = static_cast<EquipmentPiece*>(rawMemory); 
// 使用"placement new"
for (int i = 0; i < 10; ++i) 
 new (&bestPieces[i]) EquipmentPiece(i);

注意:
你仍旧得为每一个 EquipmentPiece对象提供构造函数参数。这个技术(和指针数组的主意一样)允许你在没有默认构造函数的情况下建立一个对象数组。它没有绕过对构造函数参数的需求,实际上也做不到。如果能做到的话,就不能保证对象被正确初始化。

对于类里没有定义默认构造函数所造成的第二个问题是它们无法在许多基于模板的容器类里使用。因为实例化一个模板时,模板的类型参数应该提供一个默认构造函数,这是一个常见的要求。这个要求总是来自于模板内部,被建立的模板参数类型数组里。例如一个数组模板类:

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()  
 ... 
}

在多数情况下,通过仔细设计模板可以杜绝对默认构造函数的需求。例如标准的 vector模板(生成一个类似于可扩展数组的类)对它的类型参数没有必须有默认构造函数的要求。很多模板类没有以仔细的态度去设计。这样,没有默认构造函数的类就不能与许多模板兼容。

最后讲一下在设计虚基类时所面临的要提供默认构造函数还是不提供默认构造函数的两难决策。
不提供默认构造函数的虚基类,很难与其进行合作。因为几乎所有的派生类在实例化时都必须给虚基类构造函数提供参数。这就要求所有由没有默认构造函数的虚基类继承下来的派生类(无论有多远)都必须知道并理解提供给虚基类构造函数的参数的含义。

因为这些强加于没有默认构造函数的类上的种种限制,一些人认为所有的类都应该有默认构造函数,即使默认构造函数没有足够的数据来完整初始化一个对象。比如这个原则的拥
护者会这样修改 EquipmentPiece类:

class EquipmentPiece 
{ 
public: 
EquipmentPiece( int IDNumber = UNSPECIFIED); ... 
private: 
static const int UNSPECIFIED; // 其值代表 ID 值不确定。 
}; 

这允许这样建立 EquipmentPiece 对象:

 EquipmentPiece e; //这样合法

这样的修改使得其他成员函数变得复杂,因为不再能确保 EquipmentPiece对象进行了有意义的初始化。假设它建立一个因没有 ID 而没有意义的 EquipmentPiece 对象,那么大多数成员函数必须检测 ID 是否存在。如果不存在 ID,它们将必须指出怎么犯的错误。
不过通常不明确应该怎么去做,很多代码的实现什么也没有提供:只是抛出一个异常或调用一个函数终止程序。当这种情形发生时,很难说提供默认构造函数而放弃了一种保证机制的做法是否能提高软件的总体质量。

总结:

如果一个类的构造函数能够确保所有的部分被正确初始化,所有这些弊病都能够避免。默认构造函数一般不会提供这种保证,所以在它们可能使类变得没有意义时,尽量去避免使用它们。
使用这种(没有默认构造函数的)类的确有一些限制,但是当你使用它时,它也给你提供了一种保证:你能相信这个类被正确地建立和高效地实现

猜你喜欢

转载自blog.csdn.net/qq_44800780/article/details/106118560