今天讀《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構成的陣列,其結果是未定義的。所以,多型和指標算術不能混用,陣列物件幾乎總會涉及指標的算術運算,因而陣列和多型不要混用。