【more effective c++读书笔记】【第1章】基础议题(2)

来源:互联网 发布:淘宝3c证书怎么上传 编辑:程序博客网 时间:2024/06/07 13:37

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

1、继承的最重要性质之一就是,你可以通过指向基类的指针或者引用来操作派生类对象。但是如果你通过基类指针或者引用来操作派生类所形成的数组,它几乎绝不会按你预期般地运作。

例子:

#include<iostream>using namespace std;class Base{public:Base(int i=1){ ib = i; }friend ostream& operator<< (ostream& os,Base& b){os << b.ib << " ";return os;}private:int ib;};class Derived :public Base{public:Derived(int i = 2, int j = 4,char c='0') :Base(i),id(j),cd(c){}private:int id;char cd;};void printArray(ostream& os, Base array[], int numberElements){for (int i = 0; i < numberElements; ++i)os << array[i];}int main(){Base bArray[5];printArray(cout, bArray, 5);//1 1 1 1 1,运行良好cout << endl;Derived dArray[5];printArray(cout, dArray, 5);//2 4 -858993616 2 4,运行异常cout << endl;system("pause");return 0;}

为什么第二个运行异常?因为array[i]其实是一个指针算术表达式的简写,它所代表的是*(array + i)。(array + i)与array之间的距离是i*sizeof(数组中的对象),printArray函数中参数array声明为类型是Base的数组,所以数组中的每个元素必然是Base对象,所以(array + i)与array之间的距离是i*sizeof(Base)。通常继承类对象比其基类对象大,所以编译器为printArray函数所产生的指针算数表达式,对于继承类对象所组成的数组而言就是错误的。

2、同理,尝试通过一个基类指针删除一个由派生类对象组成的数组,那么上述问题会以另一种形式出现。delete[] array;操作会调用父类的析构函数,而不会调用派生类的析构函数。C++语言规范中说,通过基类指针删除一个由派生类对象组成的数组,其结果未定义。所以多态和指针算术不能混用。数组对象几乎总是会设计指针的算术运算,所以数组和多态不要混用。解决该问题的方法是,避免让一个具体类继承自另一个具体类,这样可以带来许多好处。


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

一、缺乏default constructor可能出现以下问题:

1、无法产生数组,解决方法由以下三个:

a、使用non-heap数组

b、使用指针数组

c、先为数组分配原始内存,然后使用placement new在分配的内存上构造对象

例子:

#include<iostream>#include<string>using namespace std;class EquipmentPiece{public:EquipmentPiece(int IDNumber) :id(IDNumber){}int getID()const{ return id; }private:int id;};int main(){//EquipmentPiece equipArray[5];//错误,不存在默认构造函数//EquipmentPiece* pEquipArray = new EquipmentPiece[5];//错误,理由同上//解决方法一,缺点是不能延伸至heap数组int ID1 = 1, ID2 = 2, ID3 = 3, ID4 = 4, ID5 = 5;EquipmentPiece equipArray[] = { EquipmentPiece(ID1),EquipmentPiece(ID2),EquipmentPiece(ID3),EquipmentPiece(ID4),EquipmentPiece(ID5)};for (int i = 0; i < 5; ++i)cout << equipArray[i].getID() << " ";cout << endl;//解决方法二,缺点是必须删除数组所指的所有对象,//所需内存总量比较大,需要一些空间来放指针typedef EquipmentPiece* PEP;PEP pep[10];//正确for (int i = 0; i < 10; ++i){pep[i] = new EquipmentPiece(i);cout << pep[i]->getID() << " ";}cout << endl;for (int i = 0; i < 10; ++i)delete pep[i];PEP* ppep = new PEP[6];for (int i = 0; i < 6; ++i){ppep[i] = new EquipmentPiece(i);cout << ppep[i]->getID() << " ";}cout << endl;delete[] ppep;//解决方法三,缺点是大部分程序员不熟悉,维护困难//对象结束生命时必须手动调用析构函数,最后调用operator delete[]的方式释放rawMemoryvoid* rawMemory = operator new[](8 * sizeof(EquipmentPiece));//让pMem指向这块内存,使这块内存被视为一个EquipmentPiece数组EquipmentPiece* pMem = static_cast<EquipmentPiece*>(rawMemory);//利用palcement new构造对象for (int i = 0; i < 8; ++i){new (&pMem[i]) EquipmentPiece(i);cout << pMem[i].getID() << " ";}cout << endl;for (int i = 9; i >= 0; --i)pMem[i].~EquipmentPiece();operator delete[](rawMemory);system("pause");return 0;}

2、不适用于许多template-based container classes。对templates而言,被实例化的目标类型必须要有一个default constructors。

3、virtual base classes如果缺乏default constructors,与之合作是一种刑法。virtual base classes的自变量必须由欲产生的对象的派生层次最深的class提供。一个缺乏default constructor的virtual base class,要求其所有的derived class都必须了解其意义,并且提供virtual base class的constructor自变量。

二、提供default constructor可能出现以下问题:

1、造成class内的其他member funcitons变得复杂。 

2、影响classes的效率。如果成员函数必须测试字段是否真被初始化,其调用者就得为此付出更多的时间,并未测试代码付出空间代价。

总结:如果默认构造函数无法保证对象的所有字段被正确初始化,就不要提供默认构造函数。虽然这可能对classes的使用造成一些限制,但可以保证这样的classes产生出的对象被正确初始化,实现上也富有效率。


0 0
原创粉丝点击