2011-07-20 49 views
1

使用boost :: scoped_ptr來存放引用時,不會調用派生對象的析構函數。它使用boost :: shared_ptr。爲什麼boost :: scoped_ptr在繼承方案中不起作用?

#include "stdafx.h" 
#include <iostream> 
#include "boost/scoped_ptr.hpp" 
#include "boost/shared_ptr.hpp" 

using namespace std; 

class Base 
{ 
public: 
    Base() { cout << "Base constructor" << endl ; } 
    ~Base() { cout << "Base destructor" << endl; } 
}; 

class Derived : public Base 
{ 
public: 
    Derived() : Base() { cout << "Derived constructor" << endl; } 
    ~Derived() { cout << "Derived destructor" << endl; } 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    boost::scoped_ptr<Base> pb; // replacing by shared_ptr does call Derived destructor 
    pb.reset(new Derived()); 
    cout << "Program ends here" << endl; 
} 

你能解釋一下嗎?是否有一個「黃金法則」不使用scoped_ptr多態成員變量?

回答

18

爲什麼它適用於shared_ptr的原因是因爲它實現了一個特殊的構造和reset()方法是這樣的:

template<class T> 
class shared_ptr 
{ 
public: 
    // ... 
    // Note use of template 
    template<class Y> explicit shared_ptr(Y * p); 
    // .... 
    // Note use of template 
    template<class Y> void reset(Y * p); 
    // .... 
}; 

當這個構造函數或reset()被調用,shared_ptr「記住」原始的類型Y這樣當參考計數變爲零時,它會正確呼叫delete。 (當然p必須轉換爲T。)This is explicitly stated in the documentation

【這個構造函數已更改爲模板,以記住 傳遞的實際的指針類型。析構函數會調用 相同的指針刪除,即使T的 沒有虛擬析構函數,或者是void也是如此。 ...]

scoped_ptr constructorreset()是這樣的:

template<class T> 
class scoped_ptr : noncopyable 
{ 
public: 
    // ... 
    explicit scoped_ptr(T * p = 0); 
    // ... 
    void reset(T * p = 0); 
}; 

所以沒有辦法爲scoped_ptr 「記住」 什麼原始類型了。而當談到時間delete指針,it essentially does this

delete this->get(); 

和。所以,如果scoped_ptr點的東西,這不是一個T但實際上的T一個子類,你需要實現一個virtual析構函數:

class Base 
{ 
public: 
    Base() { cout << "Base constructor" << endl ; } 
    virtual ~Base() { cout << "Base destructor" << endl; } 
}; 

那麼,爲什麼不scoped_ptr實現一個特殊的構造對於這種情況就像shared_ptr呢?因爲"scoped_ptr template is a simple solution for simple needs"shared_ptr做了很多簿記來實現其廣泛的功能。請注意,intrusive_ptr也不會「記住」指針的原始類型,因爲它意味着儘可能輕量級(一個指針)。

+2

關於「爲什麼」的附加信息使得這是一個很好的答案。它提供了我需要的信息。謝謝! – Emile

4

您需要有一個虛擬析構函數才能通過指向其基類的指針調用派生類析構函數。

8

shared_ptr<>不同,scoped_ptr<>不會「記住」您傳遞給其構造函數的確切類型。該http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htmsynopsis講述:

template<class T> class scoped_ptr : noncopyable { 

public: 
typedef T element_type; 

explicit scoped_ptr(T * p = 0); // never throws 
~scoped_ptr(); // never throws 

void reset(T * p = 0); // never throws 

T & operator*() const; // never throws 
T * operator->() const; // never throws 
T * get() const; // never throws 

operator unspecified-bool-type() const; // never throws 

void swap(scoped_ptr & b); // never throws 

};

I.e.它不知道你通過了什麼,它只知道T,你的情況是Base。爲了使正確的刪除,您可能需要使用shared_ptr<Base>是否會適合你的設計,或者您必須在Base有一個虛析構函數

class Base 
{ 
public: 
    Base() { cout << "Base constructor" << endl ; } 
    virtual ~Base() { cout << "Base destructor" << endl; } 
}; 

作爲一個經驗法則(見邁爾斯):

如果要通過基類以多態方式刪除,使基類析構函數具有虛擬性。

不像scoped_ptr<>shared_ptr<>explicitly remembers你傳遞給構造函數的指針類型:

... 
template<class Y> shared_ptr(shared_ptr<Y> const & r); 
... 

和醫生說

此構造已更改爲模板,以記實際的指針類型通過。即使T沒有虛擬析構函數,析構函數也會使用相同的指針調用delete,並完成其原始類型,或者是void。

這是通過將運行時與靜態多態混合在一起而實現的。

+0

我怎麼能忘記讓析構函數變成虛擬的。感謝您解釋爲什麼它與shared_ptr一起工作。 – Emile