所谓default constructor(也就是说不给任何自变量就可调用者)是C++一种“无中生有”的方式。
Constructors 用来将对象初始化,所以default constructors的意思是在没有任何外来信息的情况将对象初始化。
有时候可以想象,例如,数值之类的对象,可以被合理地初始化为0或一个无意义值。其他诸如指针之类的对象亦可被合理地初始化为 mull或无意义值。数据结构如linked lists,hash tables,maps 等,可被初始化为空容器。
但是并非所有对象都落入这样的分类。有许多对象,如果没有外来信息,就没办法执行一个完全的初始化动作。
例如,一个用来表现通信簿字段的class,如果没有获得外界指定的人名,产生出来的对象将毫无意义。在某些公司,所有仪器设备都必须贴上一个识别号码。为这种用途(用以模拟出仪器设备)而产生的对象,如果其中没有供应适当的ID号码,将毫无意义。
在一个完美的世界中,凡可以“合理地从无到有生成对象”的classes,都应该内含 default constructors,而“必须有某些外来信息才能生成对象”的classes,则不必拥有default constructors。
但我们的世界毕竟不是完美的世界,所以我们必须纳入其他考虑。更明确地说,如果class 缺乏一个default constructor,当你使用这个class时便会有某些限制。
考虑下面这个针对公司仪器而设计的class,在其中,仪器识别码是一定得有的个 constructor 自变量:
class EquipmentPiece
{
public:
EquipmentPiece(int IDNumber);
...
};
由于 EquipmentPiece 缺乏 default constructor,其运行可能在3种情况下出现问题。
第一个情况是在产生数组的时候。一般而言没有任何方法可以为数组中的对象指定 constructor 自变量,所以几乎不可能产生一个由 EquipmentPiece objects构成的数组:
EquipmentPiece bestPieces[10];//错误!无法调用EquipmentPiece ctors。EquipmentPiece *bestPieces =new EquipmentPiece[10];//错误!另有一些问题。
有3个方法可以侧面解决这个束缚。
第一个方法是使用non-heap 数组,于是便能够在定义数组时提供必要的自变量:
int IDl, ID2, ID3,...,ID10;
//变量,用来放置仪器识别代码。EquipmentPiece bestPieces []={//很好,ctor 获得了必要的自变量。
EquipmentPiece(ID1),
EquipmentPiece(ID2),
EquipmentPiece(ID3),
//...
EquipmentPiece(ID10)
};
不幸的是此法无法延伸至heap 数组。
更一般化的做法是使用“指针数组”而非“对象数组”
typedef EquipmentPiece* PEP;
// PEP 是个指向 EquipmentPiece的指针。PEP bestPieces[10];
//很好,不需要调用ctor。PEP *bestPieces =new PEP[10];//也很好。
数组中的各指针可用来指向一个个不同的 EquipmentPiece object:
for (int i = 0;i<10;++i)
bestPieces[i] = new EquipmentPiece( ID Number );
此法有两个缺点。
第一,你必须记得将此数组所指的所有对象删除。如果你忘了,就会出现resource leak(资源泄漏)问题;
第二,你需要的内存总量比较大,因为你需要一些空间用来放置指针,还需要一些空间用来放置 EquipmentPiece objects。
“过度使用内存”这个问题可以避免,方法是先为此数组分配raw memory,然后使用“placement new”(见条款8)在这块内存上构造 EquipmentPiece objects。
//分配足够的raw memory,给一个预备容纳 10个 EquipmentPiece// objects 的数组使用。
void *rawMemory =operator new[](10*sizeof(EquipmentPiece));//让 bestPieces 指向此块内存,使这块内存
//被视为一个EquipmentPiece 数组。
EquipmentPiece *bestPieces =
static_cast<EquipmentPiece*>(rawMemory);//利用“placement new”构造这块内存中的 EquipmentPiece objects。
for (int i= 0;i <10;++i)
new (&bestPieces[i]) EquipmentPiece( ID Number );
注意,你还是必须供给constructor 一个自变量,作为每个 EquipmentPiece objects 的初值。这项技术(以及“由指针构成数组”的主意)允许你在“缺乏default constructor”的情况下仍能产生对象数组;但并不意味你可以因此回避供给constructor自变量。
噢,那是不可能的。如果可能,constructors的目标便受到了严厉的挫败,因为constructors 保证对象会被初始化。
placement new 的缺点是,大部分程序员不怎么熟悉它,维护起来比较困难。
此外,你还得在数组内的对象结束生命时,以手动方式调用其destructors,
最后还得以调用operator delete[](见条款8)的方式释放 raw memory:
//将bestPieces 中的各个对象,以其构造顺序的相反顺序析构掉。
for (int i = 9;i >= 0;--i)
bestPieces[i].~EquipmentPiece();
//释放 raw memory。
operator delete[](rawMemory);
如果你对rawMemory采用一般的数组删除语法,程序行为将不可预期。因为,删除一个不以new operator 获得的指针,其结果没有定义:
delete [] bestPieces;
//没有定义!因为 bestPieces
// 并非来自 new operator。
关于 new operator 和placement new,以及它们如何与 constructors 和destructors互动,细节请见条款8。
Classes 如果缺乏default constructors,带来的第二个缺点是:它们将不适用于许多template-based container classes.
对那些 templates 而言,被实例化(instantiated)的“目标类型”必须得有一个default constructors。这是一个普遍的共同需求,因为在那些templates内几乎总是会产生一个以“template 类型参数”作为类型而架构起来的数组。
例如,一个为Array class 而写的template 可能看起来像这样:
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()。
//...
}
大部分情况下,如果谨慎设计 template,可以消除对default constructor 的需求。
例如,标准的vector template(会产生出行为类似“可扩展数组”的各种classes),就不要求其类型参数拥有一个default constructor。
不幸的是许多templates的设计什么都有,独缺谨慎。
因此缺乏defaulfconstructors的classes 将不兼容于许多(不够严谨的)templates。
当C++ 程序员学到更多的template 设计技术与观念后,这个问题的重要性应该会降低。至于这一天什么时候才会到来,嗯,每个人猪测的都不一样。
虚基类必须要有默认构造函数
到底“要还是不要”提供一个default constructor 呢?就像哈姆雷特的难题一样,to be or not to be?在进退维谷的情况下,最后一个考虑点和virtual base classes(见条款E43)有关。
Virtual base classes 如果缺乏 default constructors,与之合作将是一种刑罚。
因为virtual base class constructors的自变量必须由欲产生的对象的派生层次最深(所谓 most derived)的class 提供。
于是,一个缺乏 default constructor的virtual base class,要求其所有的 derived classes——不论距离多么遇远——都必须知道、了解其意义,并且提供 virtual base class 的constructors 自变量。derived classes 的设计者既不期望也不欣赏这样的要求。
由于“缺乏 default constructors”带来诸多束缚,有些人便认为所有classes都应该提供 default constructors——甚至即使其default constructor 没有足够信息将对象做完整的初始化。依照这样的哲学,EquipmentPiece可能会被修改如下:
class EquipmentPiece
{
public:
EquipmentPiece (int IDNumber = UNSPECIFIED);
...
private:
static const int UNSPECIFIED;//一个魔术数字,
//意味没有被指定ID值
};
这就允许 EquipmentPiece objects 被这样产生出来:
EquipmentPiece e;//现在,这样可行。
这几乎总是会造成class 内的其他member functions变得复杂,因为这便不再保证一个 EquipmentPiece object 的所有字段都是富有意义的初值。
如果“一个无ID 值的 EquipmentPiece object”竟然是可以生存的,大部分member functions便必须检查ID是否存在,否则就会出现大问题。
通常,这部分的实现策略并不明朗,许多编译器选择的解决办法是:什么都不做,仅提供便利性,它们抛出一个exception,或是调用某函数将程序结束掉。
这样的事情一旦发生,我们实在很难认为软件的整体质量会因为“为一个不需要default constructor 的class画蛇添足地加上一个default constructor”而获得提升。
添加无意义的default constructors,也会影响classes 的效率。如果member functions必须测试字段是否真被初始化了,其调用者便必须为测试行为付出时间代价,并为测试代码付出空间代价,因为可执行文件和程序库都变大了。
万一测试结果为否定,对应的处理程序又需要一些空间代价。如果class constructors 可以确保对象的所有字段都会被正确地初始化,上述所有成本便都可以免除。如果default constructors无法提供这种保证,那么最好避免让default constructors 出现。
虽然这可能会对classes的使用方式带来某种限制,但同时也带来一种保证:当你真的使用了这样的classes,你可以预期它们所产生的对象会被完全地初始化,实现上亦富有效率。