条款3 绝对不要以多态(polymorphically)方式处理数组
文章目录
- 条款3 绝对不要以多态(polymorphically)方式处理数组
- 继承
- Example 打印基类-派生类数组
- 传入BalencedBST 数组到函数
- 删除基类-派生类数组
- >>>>> 欢迎关注公众号【三戒纪元】 <<<<<
继承
继承(inheritance)最重要的性质之一就是可以通过“指向 base class objects”的 pointers 或 references,来操作 derived class objects。这就是多态(polymorphically)行为。
Example 打印基类-派生类数组
假设有个 基类 class BST(binary search tree) 以及一个继承自BST的 class BalancedBST:
class BST { ... };
class BalancedBST: public BST { ... };
在正式的代码中,BST及其基类可能会被设置为模板类(templates)。
考虑有个函数可以打印 BSTs 数组中的每一个 BST 内容:
void PrintBSTArray(ostream& s, const BST array[], int numElements) {
for (int i = 0; i < numElements; ++i) {
s << array[i]; // 假设 BST objects 有1个 operator<< 可用
}
}
当你将1个由BST 对象组成的数组传递给此函数,是OK的
BST BSTArray[10];
...
PrintBSTArray(cout, BSTArray, 10); // OK
array[i]
是1个“指针算术表达式”的简写,代表的是 *(array + i)
array
是个指针,指向数组起始处。
Q:array
所指内存和 array + i
所指内存两者相距多远?
A:i * sizeof(数组中对象)
。因为 array[0]
与array[i]
之间有i
个对象。
传入BalencedBST 数组到函数
加入传递到PrintBSTArray
函数 的是1个由 BalancedBST 对象组成的数组,编译器会被误导
编译器假设数组中每个元素的大小是 BST 的大小,但其实每个元素大小是 BalancedBST 的大小。
由于 derived classes 通常比 base classes 有更多的 data members,所以 derived classes objects 通常都比 base classes objects 来的大。
因此,编译器为PrintBSTArray 函数所产生的指针算术表达式,对于 BalancedBST objects 所组成的数组而言就是错误的。结果不可预期。
测试:
class BST {
public:
int a{0};
};
class BalancedBST : public BST {
public:
int b{1};
int c{2};
};
int main() {
BST *bst = new BST[22];
std::cout << "print BST array:" << std::endl;
PrintBSTArray(bst, 5);
BalancedBST* balTreeArray = new BalancedBST[33];
std::cout << "print balTreeArray array:" << std::endl;
PrintBSTArray(balTreeArray, 5);
}
结果:
print BST array
0
0
0
0
0
print balTreeArray array
0
1
2
0
1
删除基类-派生类数组
同理,如果尝试通过一个 base class 指针,删除1个由 derived class objects 组成的数组,同样存在上述问题:
// 删除1个数组,首先记录关于删除动作的消息
void DeleteArray(ostream& logStream, BST array[]) {
logStream << "Deleting array at address "
<< static_cast<void*>(array) << '\n';
delete [] array;
}
// 产生1个 BalancedBST 数组
BalancedBST *balTreeArray = new BalancedBST[33];
... // work
DeleteArray(cout, balTreeArray);
上述代码,仍然存在1个“指针算术表达式”。
当数组被删除,数组中每1个元素的 destructor 都必须被调用,编译器其实看到的这样的句子delete[] array;
,相当于看到:
// 将 *array 中的对象以其构造顺序的相反顺序加以析构
for (int i = the number of elements in the array - 1; i >= 0; --i) {
array[i].BST::~BST();
}
C++ 语言规范说,通过 base class 指针删除1个由 derived class objects 构成的数组,其结果未定义。
简单的说,多态(polymorphism) 和指针数组不能混用。
测试:
int main() {
BST* bst = new BST[2];
std::cout << "print BST array:" << std::endl;
PrintBSTArray(bst, 5);
DeleteArray(bst);
BalancedBST* balTreeArray = new BalancedBST[3];
std::cout << "print balTreeArray array:" << std::endl;
PrintBSTArray(balTreeArray, 5);
DeleteArray(balTreeArray);
std::cout << "Finish!" << std::endl;
return 0;
}
结果:
print BST array:
0
0
0
0
1041
BST destructor.
BST destructor.
print balTreeArray array:
0
1
2
0
1
BST destructor.
BST destructor.
BST destructor.
Finish!
所以删除 derived class 的时候,会调用基类的析构函数,解决这种办法需要将基类的析构函数设为虚函数。
详见《Effective C++》的《条款7 为多态基类声明virtual析构函数》