条款4 非必要不提供 default constructor
文章目录
- 条款4 非必要不提供 default constructor
- classes 必须有默认构造函数?
- Example
- 1. 产生数组
- 解决方法
- 2.不适用于 template-based container clases
- 3.虚基函数
- 探讨
- >>>>> 欢迎关注公众号【三戒纪元】 <<<<<
classes 必须有默认构造函数?
当我们定义1个类,如果没有定义构造函数,C++会默认提供1个构造函数(Constructors),用来将对象初始化,因此,default constructors 的作用就是在没有外来信息的情况下将对象初始化。
但是有许多对象,如果没有外来信息,就没有办法执行1个完全的初始化动作:
- 一个用来表现通信簿字段的class,如果没有获得外界指定的人名,产生出来的对象将毫无意义;
- 某些公司里,所有仪器设备上都必须贴上1个识别号码,如果这个识别号码的类产生的对象没有供应适当的ID号码,将毫无意义。
一般来说,“合理地从无到有生成对象”的 classes,都应该内含 default constructors,而 “必须拥有某些外来信息才能生成对象”的 classes,则不必有 default constructor。
Example
针对公司仪器而设计的 classes,识别码是一定得有的 constructor 自变量:
class EquipmentPiece {
public:
EquipmentPiece(int IDNumber);
...
};
EquipmentPiece 缺乏 default constructor,运行时可能在3种情况下出现问题:
1. 产生数组
一般而言没有任何办法可以为数组中的对象指定 constructor 自变量,几乎不可能产生1个由 EquipmentPiece objects 构成的数组
EquipmentPiece pieces[10]; // 错误!无法调用 EquipmentPiece ctors.
EquipmentPiece *pieces = new EquipmentPiece【10】;// 错误!其他问题
解决方法
- 使用 non-heap 数组
int ID1, ID2, ID3, ..., ID10; // 变量,从来放置一起识别代码
...
EquipmentPiece pieces[] = {
EquipmentPiece(ID1),
EquipmentPiece(ID2),
EquipmentPiece(ID3),
...,
EquipmentPiece(ID10),
}
此法无法延伸至 heap 数组
- 使用“指针数组”而非“对象数组”
typedef EquipmentPiece* PEP; // PEP 是个指向 EquipmentPiece 的指针
PEP piece[10]; // good,不需要调用 ctor
PEP *pieces = new PEP[10]; // good
// 各指针指向不同的 EquipmentPiece object
for (int i = 0; i < 10; ++i) {
piece[10] = new EquipmentPiece(ID Number);
}
这种方法有2个缺点:
- 必须记得将数组所指的所有对象删除,如果忘了就会导致资源泄露;
- 需要的内存总量比较大,因为需要一些空间来存放指针,还需要一些空间放置EquipmentPiece objects
而过度使用内存这个问题可以避免,可以先为数组分配 raw memory,然后使用 placement new 在这块内存上构造 EquipmentPiece objects
// 分配足够的 raw memory, 给1个预备容纳10个 EquipmentPiece objects 的数组使用。
void *rawMemory = operator new[](10*sizeof(EquipmentPiece));
// 让pieces 指向此块内存,使这块内存被当做1个 EquipmentPiece 数组
EquipmentPiece *pieces = static_cast<EquipmentPiece*>(rawMemory);
// 利用“placement new” 构造这块内存中的 EquipmentPiece objects
for(int i = 0; i < 10; ++i) {
new (&pieces[i]) EquipmentPiece( ID Number );
}
这里还是必须提供给 constructor 1个自变量,作为每个EquipmentPiece object 的初值。
这项技术允许在缺乏“default constructor”的情况下仍然能产生对象数组;并不意味着可以回避提供 constructor 自变量。
placement new 必须手动调用 destructors, 调用 operator delete[] 释放 raw memory:
// 以构造顺序的相反顺序析构
for(int i = 9; i >= 0; --i) {
pieces[i].~EquipmentPiece();
}
// 释放 raw memory
operator delete[](raw memory);
2.不适用于 template-based container clases
对于 templates 而言,被实例化的“目标类型”必须得有1个defatut constructorus
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.
3.虚基函数
Virtual base classes 如果缺乏 default constructors,则 Virtual base classes constructors 的自变量必须由欲产生的对象的派生层次最深(most derived)的class 提供。
一个缺乏 default constructors 的 virtual base class,要求其所有的 derived classes —— 不论距离多远 —— 都必须知道且了解其意义,并提供 virtual base class 的 constructors 自变量。
探讨
To be or not to be is a question
所以到底应不应该提供 default constructors?
如果允许 defatult constructor,那么 无法保证 EquipmentPiece object 的所有字段都有富有意义的初值。
大部分 member functions 必须检查 ID 是否存在,否则会出问题。通常这部分的策略不明朗。
添加毫无意义的 defatult constructor ,会影响 classes 的效率。如果 member functions 必须测试字段是否真被初始化了,其调用者必须为测试行为付出时间代价,并为测试代码付出空间代价,因为其可执行文件和程序库都变大了。
如果 class constructors 可以确保对象所有字段都会被正确初始化,上述成本都可以免除。如果无法保证,最好避免 default constructor 出现。