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


    条款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[]的方式释放rawMemory
    	void* 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产生出的对象被正确初始化,实现上也富有效率。


    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    win10 L2TP拨号
    Esxi直通板载Sata
    Esxi 增加网卡驱动 生成ISO
    IPMITOOL THRESHOLD 修改
    Reverse Engineering Supermicro IPMI
    Esxi通过RDM直通硬盘
    Centos 安装后优化
    Centos 6安装apache 2.4
    Try Catch Finally 中Finally的代码在什么时候不被执行
    用CutePDF AND GhostScript 生成PDF的处理流
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785428.html
Copyright © 2020-2023  润新知