条款3:绝对不用以多态(polymorphically)方式处理数组

一、

1.1  继承(inheritance)的重要性质之一就是:你可以通过“指向  base class objects"的pointers或reference,来操作derived class objects。如此的pointers和references,我们说其行为是多态的(polymorphically)——犹如它们有多重类型似的。C++也允许你通过base class的pointers和references来操作“derived class objects 所形成的数组”。但这一点也不值得沾沾自喜,因为它几乎绝不会如你所预期般地运作。

举一个例子,假设你有一个class BST(意思是binary search tree)及一个继承自BST

的class BalancedBST:

class BST{…};

class BalanceBST:public BST{…};

在一个真正规模的程序中,这样的classes 可能会呗设计为templates,这不是此处的重点;如果加上template各种语法,反而使程序更难阅读。针对目前的讨论,我假设BST和BalanceBST都只含ints。

现在考虑有一个函数,用来打印BSTs数组中的每一个BST的内容:

void printBSTArray(ostream& s,const BST array[],int numElements){

for(int i=0;i<numElements;++i){s<<array[i];//假设BST object有一个}//operator<<可用。

}

当你将一个由BST对象组成的数组传给此函数,没问题:

BST BSTArray[10];

printBSTArray(cout,BSTArray,10);     //运行良好。

然而如果你将一个BalancedBST对象所组成的数组交给printBSTArray(cout,bBSTArray,10);//可以正常运行吗?

你的编译器会毫无怨言地接受它,但是看看这个循环(就是稍早出现的那个)。

for(int i=0;i<numElenents;++i)

s<<array[i];

array[i]其实是一个“指针算术表达式”的简写:它代表的其实是*(array+i)。我们知道,array是个指针,指向数组起始处。

array所指内存和array+i所指内存两者相距多远?

    答案是i*sizeof(数组中的对象)

    因为array[0]和array[i]之间有i个对象。为了让编译器所产生的代码能够正确走访整个数组,编译器必须有能力决定数组中的对象大小。

很容易呀,参数array不是被声明为“类型BST”的数组吗?所以数组中的每个元素必然都是BST对象,所以array和array+之间的距离一定是i*sizeof(BST)。

至少你的编译器是这么想的。但如果你交给printBstArray函数一个由BalancedBST对象组成的数组,你的编译器就会被误导。这种情况下它仍假设数组中每一个元素的大小上BST的大小,但其实每一元素的大小是BalanceBST的大小。由于derived

classes 通常比其有更多的data members,所以derived class objects通常比其base class objects来得大。因此,我们可以合理地预期一个BlanceBST object比一个BST object大。如果是这样,编译器为printBSTArray函数所产生的指针算术表达式,对于 BalancedBST objects所组成的数组而言就是错误的。至于会发生什么结果,不可预期。无论如何,结果不会令人愉快。

1.2如果你尝试通过一个base class指针,删除一个由derived class objects组成的数组,那么上述问题还会以另一种不同面貌出现,下面是你可能做出的错误尝试:

//删除一个数组,但是首先记录一个有关此删除动作的消息。

void deleteArray(ostream& logStream,BST array[])

{

    logstream<<"Deleting array at address "

                    <<static_cast<void*>(array)<<'\n';

    delete []array;

}

BalanceBST *balTreeArray=                   //产生一个BalancedBST数组。

new BalanceBST[50];

...

deleteArray(cout,balTreeArray);//记录此删除动作。

虽然你没有看到,但其中一样有“指针算术表达式”的存在。是的,当数组被删除,数组中每一个元素的destructor都必须被调用(见条款8),所以当编译器看到这样的句子:

delete []array;

必须产生出类似这样的代码:

//将*array 中的对象以其构造顺序的相反顺序加以析构。

for(int i=the number of elements int the array-1;i>=0;--i)

{

    array[i].BST::~BST();         //调用array[i]的destructor.

}

如果你这么写,便是一个行为错误的循环。编译器如果产生类似代码,当然同样是一个行为错误的循环。C++语言规范中说,通过base class指针删除一个由derived classes objects构成的数组,其结果是未定义的。我们知道所谓“未定义”的意思就是:

执行后会产生苦恼。简单地说,多态(polymorphism)和指针算术不能混用。

数组对象几乎总是会涉及指针的算术运算,所以数组和多态不要混用。

注意,如果你避免让一个具体类(如本例之BalancedBST)继承自另一个具体来(如本例之BST),你就不太能够犯“以多态方式来处理数组”的错误。如条款33所说,设计你的软件使“具体类不要继承来自另一个具体类”,可以带来许多好处。

应看看33条款的完整内容(推荐)

文章内容来源《More Effective C++》侯捷作为自己学习笔记之用,难免有错漏之处,详细内容请看图书及C++ reference

猜你喜欢

转载自blog.csdn.net/idealhunting/article/details/82746665