More Effective C++ 條款3 (轉)
條款3:不要使用多型性陣列
類繼承的最重要的特性是你可以透過基類指標或引用來操作派生類。這樣的指標或引用具有行為的多型性,就好像它們同時具有多種形態。C++允許你透過基類指標和引用來操作派生類陣列。不過這根本就不是一個特性,因為這樣的程式碼根本無法如你所願地那樣執行。:namespace prefix = o ns = "urn:schemas--com::office" />
假設你有一個類BST(比如是搜尋樹)和繼承自BST類的派生類BalanceST:
class BST { ... };
class BalancedBST: public BST { ... };
在一個真實的裡,這樣的類應該是模板類,但是在這個例子裡並不重要,加上模板只會使得程式碼更難閱讀。為了便於討論,我們假設BST和BalancedBST
只包含int型別資料。
有這樣一個,它能列印出BST類陣列中每一個BST物件的內容:
void printBSTArray(ostream& s,
const BST array[],
int numElements)
{
for (int i = 0; i < numElements; ) {
s << array[i]; //假設BST類
} //過載了運算子<<
}
當你傳遞給該函式一個含有BST物件的陣列變數時,它能夠正常執行:
BST BSTArray[10];
...
printBSTArray(cout, BSTArray, 10); // 執行正常
然而,請考慮一下,當你把含有BalancedBST
物件的陣列變數傳遞給printBSTArray函式時,會產生什麼樣的後果:
BalancedBST bBSTArray[10];
...
printBSTArray(cout, bBSTArray, 10); // 還會執行正常麼?
你的將會毫無警告地編譯這個函式,但是再看一下這個函式的迴圈程式碼:
for (int i = 0; i < numElements; ) {
s << array[i];
}
這裡的array[I]只是一個指標演算法的縮寫:它所代表的是*(array)。我們知道array是一個指向陣列起始地址的指標,但是array中各元素地址與陣列的起始地址的間隔究竟有多大呢?它們的間隔是i*sizeof(
一個在陣列裡的物件)
,因為在array陣列[0]到[I]間有I個物件。編譯器為了建立正確遍歷陣列的程式碼,它必須能夠確定陣列中物件的大小,這對編譯器來說是很容易做到的。引數array被宣告為BST型別,所以array陣列中每一個元素都是BST型別,因此每個元素與陣列起始地址的間隔是be i*sizeof(BST)
。
至少你的編譯器是這麼認為的。但是如果你把一個含有BalancedBST
物件的陣列變數傳遞給printBSTArray
函式,你的編譯器就會犯錯誤。在這種情況下,編譯器原先已經假設陣列中元素與BST物件的大小一致,但是現在陣列中每一個物件
大小卻與BalancedBST
一致。派生類的長度通常都比基類要長。我們料想BalancedBST物件長度的比BST長。如果如此的話,printBSTArray函式生成的指標演算法將是錯誤的,沒有人知道如果用BalancedBST陣列來執行printBSTArray函式將會發生什麼樣的後果。不論是什麼後果都是令人不愉快的。
如果你試圖刪除一個含有派生類物件的陣列,將會發生各種各樣的問題。以下是一種你可能的不正確的做法。
//刪除一個陣列, 但是首先記錄一個刪除資訊
void deleteArray(ostream& logStream, BST array[])
{
logStream << "Deleting array at address "
<< static_cast
delete [] array;
}
BalancedBST
*balTreeArray = // 建立一個BalancedBST
物件陣列
new BalancedBST
[50];
...
deleteArray(cout, balTreeArray); // 記錄這個刪除操作
這裡面也掩藏著你看不到的指標演算法。當一個陣列被刪除時,每一個陣列元素的解構函式也會被。當編譯器遇到這樣的程式碼:
delete [] array;
它肯定象這樣生成程式碼:
// 以與構造順序相反的順序來
// 解構array陣列裡的物件
for ( int i = 陣列元素的個數 1; i >= 0;--i)
{
array[i].BST::~BST(); // 呼叫 array[i]的
} // 解構函式
因為你所編寫的迴圈語句根本不能正執行,所以當編譯成可執行程式碼後,也不可能正常執行。語言規範中說透過一個基類指標來刪除一個含有派生類物件的陣列,結果將是不確定的。這實際意味著執行這樣的程式碼肯定不會有什麼好結果。多型和指標演算法不能混合在一起來用,所以陣列與多型也不能用在一起。
值得注意的是如果你不從一個具體類(concrete classes)(例如BST
)派生出另一個具體類(例如BalancedBST),那麼你就不太可能犯這種使用多型性陣列的錯誤。正如條款33所解釋的,不從具體類派生出具體類有很多好處。我希望你閱讀一下條款33的內容。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990051/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Effective C++ 條款08_不止於此C++
- Effective Modern C++ 系列之 條款2: autoC++
- Effective c++條款11:在operator=中處理“自我賦值”C++賦值
- [讀書筆記][effective C++]條款30-inline的原理筆記C++inline
- Effective C++ 筆記(3)資源管理C++筆記
- 文獻學習——Making Deduction More Effective in SAT Solvers
- 學懂現代C++——《Effective Modern C++》之轉向現代C++C++
- effective C++ : CHAPTER 8C++APT
- 【Effective Modern C++】索引C++索引
- Effective C++筆記C++筆記
- effective C++筆記1C++筆記
- 《Effective C++》讀書筆記C++筆記
- 《Effective C++》第三版-3. 資源管理(Resource Management)C++
- Effective C++ 4.設計與宣告C++
- 【Effective STL(3)】關聯容器
- 條款01: 視C++為一個語言聯邦C++
- 《Effective C++》閱讀總結(三):資源管理C++
- 《Effective C++》第三版-1. 讓自己習慣C++(Accustoming Yourself to C++)C++
- 學懂現代C++——《Effective Modern C++》之型別推導和autoC++型別
- 條款05: 瞭解c++默默編寫並呼叫哪些函式C++函式
- 《Effective C++》第三版-5. 實現(Implementations)C++
- 讀完Java名著《Effective Java》: 我整理了這50條技巧Java
- 《Effective C++》閱讀總結(四): 設計、宣告與實現C++
- Effective Python(3)- 瞭解 bytes 與 str 的區別Python
- [20180920]等待事件SQLNet more data from client 3.txt事件SQLclient
- c++切面條題目C++
- 《Effective C++》第三版-4. 設計與宣告(Design and Declarations)C++
- 《Effective Python 第二版》第二條 遵循PEP8風格指南Python
- c++:-3C++
- 《Effective C++》閱讀總結(二):類的構造、析構和賦值C++賦值
- 《看雪服務條款》
- 【C++】C++之型別轉換C++型別
- C++中的條件變數C++變數
- MORE_DETAIL_TECHAI
- TypeScript 之 More on FunctionsTypeScriptFunction
- More web function requests go online concurrently, and web service deployment is faster and more economical!WebFunctionGoAST
- effective java 觀後感Java
- DNS: More than just namesDNS
- Linux基礎命令---moreLinux