C++以多型方式處理陣列可能會遇到的問題

QingLiXueShi發表於2015-06-03

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

相關文章