More effective C++:条款3.绝对不要以多态方式处理数组及条款4:非必要不提供default construcor

条款3.绝对不要以多态方式处理数组

class BST{
    
    ...};
class BalancedBST:public BST{
    
    ...};

假设BST和BalancedBST都只含ints,现在考虑打印BSTs数组中每一个BST的内容:

void printBSTArray(ostream &s, const BST array[], int numElements){
    
    
	for(int i = 0; i < numElements; ++i){
    
    
		s << array[i];  //假设BST objects有一个operator<<可用
	}
}

如果是将一个BST对象组成的数组传给此函数,是没问题的。但是如果将一个BalancedBST对象组成的数组交给printBSTArray函数,那么就会有问题了。
具体原因如下:array[i]实际上代表的是*(array+i),由于array是BST类型,那么实际上计算array[i]内存地址的时候array[0]与array[i]的距离为i*sizeof(BST)。但由于我们传入的是BalancedBST,派生类通常比基类拥有更多的数据成员,所以这里的BalancedBST通常比BST要大,那么位置偏移的话也是要比原先的函数中计算的要大的,由于内存地址偏移的位置不对,那么产生的结果其实是不可预期的。

同样的,如果通过基类指针去删除由派生类对象构成的数组,其结果也是未定义的。当然理想的情况是通过基类指针调用每一个派生类的析构函数,但是实际情况是不会的,原因也是由于计算的时候是按照基类的大小来计算内存偏移的,所以并不能合理的找到每一个派生对象的起始地址,也就不能正确的调用析构函数。
总结:多态和指针算术不能混用,数组对象几乎总是涉及指针的算术运算,所以数组和多态不要混用。

条款4:非必要不提供default construcor

首先常用的数据结构或者指针数值之类的很多都可以设置为null或者0或者空。但是具体生活情境中很多类如果要初始化我们都要合理初始化,因为使用default constructor是没有意义的,比如生产了某个仪器设备,必须得有配套的的ID号码,否则将毫无意义。

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

那么没有default constructor,带来的问题有以下三个方面。
1.无法批量产生对象数组。解决方法:产生指针数组而非对象数组,然后对指针数组初始化的时候指向一个个不同的具体对象。要注意,要记得将数组所指对象都删除,否则出现资源泄漏的情况,占用资源较多,一是要放指针,二是要放具体对象。
过度使用内存问题可以通过先分配内存,然后使用placement new,在这块内存上进行构造。
如下所示:

//先申请容纳10个EquipmentPiece的内存
void *rawMemory = operator new[](10*sizeof(EquipmentPiece));
//让bestPieces指向这块内存,使这块内存被视为一个EquipmentPiece数组
EquipmentPiece *bestPieces = static_cast<EquipmentPiece*>(rawMemory);
//利用placement new构造这块内存的EquipmentPiece对象
for(int i=0;i<10;++i){
    
    
	new (&bestPiece[i]) EquipmentPiece( ID number);
}

placement new的缺点是程序员不太了解,且要手动调用destructors,最后还要operator delete[]释放最初申请的内存。

//将bestPieces中的各个对象,以构造顺序的相反顺序析构掉
for(int i=9;i>=0;--i){
    
    
	bestPieces[i].~EquipmentPiece();
}
//释放raw memory
operator delete[](rawMemory);

2.不适用于许多基于模板的容器类
许多基于模板的容器类可能传入了一个容器的大小之后,里面的初始化容器代码便是直接使用new来开辟这个大小的对象空间,这其中便调用了T::T()。那么便会产生错误。
3.关于虚基类
如果虚基类没有提供default constructor,那么只有一个有意义的constructor,那就要求,无论派生了多少层,都要求最深的派生类了解其含义,并且给出正确的基类的constructor自变量,因为虚基类构造函数的自变量由最深的派生类提供。这其实是件很繁琐的事。

最后,还是有人支持classes都应该提供default constructor,即使没有足够信息将对象做完整的初始化。比如允许上面的ID为魔术数字,意味着没有被指定ID值。造成的影响有:大部分成员函数需要检查ID是否存在,相应的测试代码是存在空间代价的,如果测试结果为负,那么对应的处理程序需要空间代价。
故最后的结论为:如果class constructor可以确保对象的所有字段都会被正确地初始化,那么上述所有成本就可以被免除。如果default constructor无法提供这种保证,那么最好避免让default constructor出现。

猜你喜欢

转载自blog.csdn.net/qq_43847153/article/details/127889498