今天读《More Effective C++》时遇到一个条款:绝对不要以多态方式处理数组。以前自己也没有注意过,觉得有必要记录下来。
C++是允许通过base class的指针或引用来操作derived class所形成的数组的。但发生的事情可能会令你感到意外。下面举例说明:
基类和派生类是这样的:
class BST /*base class*/ { public: BST() : x1(1) {} virtual ~BST() { cout << "Good Bye BST." << endl; } int x1; }; class BalancedBST : public BST /*derived class*/ { public: BalancedBST() : BST(), x2(2) {} virtual ~BalancedBST() { cout << "Good Bye BalancedBST." << endl; } int x2; };
下面我重载了两个输出操作符:
/*输出base class*/ ostream& operator<<(ostream& os, const BST& obj) { os << "class BST: " << obj.x1 << endl; return os; } /*输出derived class*/ ostream& operator<<(ostream& os, const BalancedBST& obj) { os << "Class BalancedBST: " << obj.x1 << ' ' << obj.x2 << endl; return os; }
下面这个函数用于输出base class和derived class的数组。
/*输出base class和derived class数组*/ void Print(ostream& os, const BST arr[], int n) { for (int i = 0; i < n; ++i) { os << arr[i]; } }
当以如下方式测试时,没有问题。
BST baseArr[10]; Print(cout, baseArr, 10); //好的,没问题,正常
当以如下方式测试时,就会出现问题。
BalancedBST deriveArr[10]; Print(cout, deriveArr, 10); //出错啦
编译器要想遍历数组中每一个元素,它必须知道每一个元素的大小。很明显,当print参数为BalancedBST数组时,编译器静态的将其数组大小当作BST的大小处理,以*(i+arr)的方式前进,结果是未知的。
还有一种情况,就是通过一个base class指针,删除一个由derived class组成的数组。
当以如下方式测试时,没有问题。
BST *base = new BST[10]; delete [] base; //好的,没有问题 BalancedBST *derived = new BalancedBST[10]; delete [] derived; //好的,没有问题
当我以如下方式测试时,就会有问题。
当数组被删除时,数组中每个元素的destructor会被调用,调用的顺序与构造顺序相反。也就是说执行delete [] base语句时,会产生类似下面的代码。
for (int i = 9; i >= 0; --i) //编译器产生类似的代码,但是是错误的。 { base[i].BST::~BST(); }
根本原因还是编译器把derived class数组成员的大小当作base class来计算数组元素的位置。
C++规定,通过base class指针删除一个由derived class objects构成的数组,其结果是未定义的。所以,多态和指针算术不能混用,数组对象几乎总会涉及指针的算术运算,因而数组和多态不要混用。