2017-09-08 36 views
2

我有以下一段代碼(來自Koening & Moo Accelerated C++ 255頁),它定義了一個通用句柄類HandleHandle用於管理對象的內存:刪除句柄類中的對象可能導致未定義的行爲

#include <iostream> 
#include <stdexcept> 

///Handle 
template <class T> 
class Handle 
{ 
    public: 
    Handle() : p(0) {} 
    Handle &operator=(const Handle &); 
    T *operator->() const; 
    ~Handle() { delete p; } 
    Handle(T *t) : p(t) {} 

    private: 
    T *p; 
}; 

template <class T> 
Handle<T> &Handle<T>::operator=(const Handle &rhs) 
{ 
    if (&rhs != this) 
    { 
     delete p; 
     p = rhs.p ? rhs.p->clone() : 0; 
    } 
    return *this; 
}; 

template <class T> 
T *Handle<T>::operator->() const 
{ 
    if (p) 
     return p; 
    throw std::runtime_error("error"); 
}; 

class test_classA 
{ 
    friend class Handle<test_classA>; 

    private: 
    virtual test_classA *clone() const { return new test_classA; } 

    public: 
    virtual void run() { std::cout << "HiA"; } 
}; 

class test_classB : public test_classA 
{ 
    private: 
    virtual test_classB *clone() const { return new test_classB; } 

    public: 
    virtual void run() { std::cout << "HiB"; } 
}; 

int main() 
{ 

    Handle<test_classA> h; 
    h = new test_classA; 
    h->run(); 

    return 0; 
} 

當我編譯這個使用g++ -o main main.cpp -Wall我得到警告:

warning: deleting object of polymorphic class type ‘test_classA’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor] 
    ~Handle() { delete p; } 

我不太明白的警告。句柄類自動刪除析構函數中的指針*p,無論它的類型如何,那麼潛在的陷阱在哪裏?

+2

偏題:留意[三規則](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-ree)。 'Handle'缺少一個拷貝構造函數。 – user4581301

+0

@ user4581301不是'處理(T * t):p(t){}'拷貝構造函數嗎? – BillyJean

+1

'句柄(T * t):p(t){}'是一個將'p'初始化爲給定值的構造函數。一個拷貝構造函數根據現有的實例創建一個新的實例,看起來像這樣:'Handle(const Handle&source)'。在這種情況下,你可能不想複製,也可能通過刪除拷貝構造函數'Handle(const Handle&source)= delete;'來替代它,否則會爲'p'創建一個新的'T' at:'Handle(const Handle&source):p(new T(source.p){}'但是你這樣做,你不需要兩個'Handles'具有相同的'p'。 – user4581301

回答

1

在C++中,如果你有一個基類(在這裏,test_classA)有其他類從它(這裏,test_classB),你必須要小心刪除test_classA類型的指針,如果這些指針可能在對象實際上指向派生類型爲test_classB。請注意,您在此處編寫的代碼中正是這樣做的。

如果你做這樣的事情,你需要給你的基類(test_classA)虛析構函數,就像這樣:

class test_classA { 
public: 
    virtual ~test_classA() = default; 
    // ... 
}; 

這樣,當C++試圖刪除test_classA類型的指針,它知道有問題的指針實際上可能不指向test_classA對象,並且會正確調用正確的析構函數。

順便說一下,此問題與您的包裝類型完全無關。您可以通過編寫來獲得相同的問題

test_classA* ptr = new test_classB; 
delete ptr; // <--- Warning! Not good unless you have a virtual dtor. 
+1

但是,這是* *不是** OP正在做什麼,在提供的代碼中,確實沒有未定義的行爲。 – SergeyA

1

你的警告說明了一切。你的類A是多態的,但析構函數是非虛擬的。當基類沒有虛擬析構函數時,通過指向基類的指針刪除派生類的對象是未定義的行爲。

在您的具體示例中,沒有未定義的行爲,因爲您沒有派生類的對象,但編譯器可能無法確定它,所以它會提示您。

1

問題是,如果處理的對象是模板實例類型的子類,則會發生錯誤的刪除。

在你的情況下,它會發生,如果你Handle<test_classA> h;將處理test_classB類型的對象......

1

手柄h具有類型Handle<test_classA>所以它會存儲指向test_classA和調用test_classA析構函數。然而,你可以在你的手柄存儲指向test_classB在這種情況下test_classB析構函數不會被調用,因爲test_classA析構函數不是虛:

h = static_cast< test_classA * >(new test_classB); 

也要注意這Handle類有不好選擇的名稱,它本質上是一個智能指針類的類。

0

警告是由於您的基類的析構函數不是虛擬的。如果你想多義地使用你的類(例如創建一個帶有基類指針的向量,其中指向的對象是派生類),你會有未定義的行爲。

另一件要提及的是,您將Handle類聲明爲類test_classA的一個朋友,以獲得對克隆函數的訪問權限。請注意,friend關鍵字不是傳遞的,所以在派生類中,Handle類無法訪問克隆函數。

最後你的克隆功能對我來說不是那麼清楚。讓我們來看看主要功能:

Handle<test_classA> h; 
h = new test_classA 
  1. 您實例使用它的默認構造函數的類手柄,該手柄初始化P設爲一個空指針(順便說一下,如果你使用C++ 11它是更好的初始化它與nullptr而不是0或NULL)
  2. 在下一行中,您調用實例h的operator =。在這裏,你的operator =等待Handle類。所以表達式new test_classA會隱式地調用你的Handle類的構造函數Handle(T *t) : p(t) {}。然後這將用在你的operator =函數中,你檢查rhs.p是否爲NULL。因爲它不是null,所以你會調用clone函數(你寫的相當於(rhs.p) - > clone,所以它不是Handle類的operator->被調用,但是指針p)會再次創建一個內存在HEAP中。

我認爲這不是你想要的。

相關問題