2013-07-08 43 views
2

我是新來C++風格的演員,並需要幫助理解下面的代碼如何工作(這是我寫的一些虛擬代碼來理解事物)。如何使用靜態轉換來管理共享對象的生命週期?

#include <iostream> 
#include <memory> 

class A { 
public: 
    A() : a(1) { 
     std::cout << "Creating A\n"; 
    } 
    ~A() { 
     std::cout << "Destroying A\n"; 
    } 
    int a; 
}; 

class B : public A { 
public: 
    B() : b(2) { 
     std::cout << "Creating B\n"; 
    } 
    ~B() { 
     std::cout << "Destroying B\n"; 
    } 
    int b; 
}; 

int main() { 
    std::shared_ptr<B> objectB(new B()); 
    { 
    std::shared_ptr<A> (static_cast<A*>(objectB.get())); 
    std::cout << "End of inner scope\n"; 
    } 
    std::cout << "End of outer scope\n"; 
} 

它打印

Creating A 
Creating B 
Destroying A 
End of inner scope 
End of outer scope 
Destroying B 
Destroying A 

我的理解:

Creating A   -> B's ctor calls base class ctor 
Creating B   -> B's ctor 
Destroying A  -> ??? 
End of inner scope 
End of outer scope 
Destroying B  -> B's dtor 
Destroying A  -> B's dtor calls base dtor 

爲什麼我得到的第一個Destroying A和究竟發生在這裏? A怎麼能被銷燬兩次?

+2

這不是一個完整的答案,但你有兩個獨立的共享指針指向相同的東西。因此,在某些時候,將有多次嘗試釋放託管對象,這是未定義的行爲。另外,嘗試使用'std :: endl'而不是'\ n'來刷新stdout緩衝區。 – juanchopanza

+0

我認爲你關於未定義行爲的觀點是有道理的,儘管我仍然試圖找到對此的參考。我知道'endl'可能有助於沖洗,但它不會改變順序。 – Lazer

+1

@Lazer:它*是* UB多次刪除同一對象(可能搜索* double-delete *將更容易找到?),這就是您的代碼中發生的情況。此外,您應該在基類中使用虛擬析構函數,以確保在通過基類指針刪除派生對象時能夠得到適當的清理。 – syam

回答

6

如果你確保輸出被刷新(通過使用std::endl,例如),你會得到

創建一個

創建乙

毀內一個

結束範圍

外部示波器的末端

銷燬乙

銷燬

的原因爲雙刪除A是,你從一個原始指針在這裏構建shared_ptr

std::shared_ptr<A> (static_cast<A*>(objectB.get())); 

shared_ptr是完全獨立的第一個,並有自己的引用計數。因此,當範圍結束時,它將嘗試刪除它所保存的指針A。如果你這樣做,而不是:

std::shared_ptr<A>{objectB}; 

那麼你不會遇到問題。請注意,此處不需要static_cast

請注意,A應該有一個virtual析構函數。 shared_ptr有一個聰明的銷燬機制,這意味着在這個例子中這不是關鍵,但是一般來說,如果你要多態刪除對象,基類必須有一個虛擬析構函數。

+0

另外,由於您在'A'或'B'上沒有虛擬析構函數,因此會調用錯誤類型的析構函數。 –

+0

@JoeZ對,雖然'shared_ptr'對此有點特別。我添加了一個關於它的註釋。 – juanchopanza

+0

'{}'語法的含義是什麼?它是否以某種特殊的方式調用構造函數? – Lazer

1

以防萬一它不是從juanchopanza的回答清楚,這條線

std::shared_ptr<A> (static_cast<A*>(objectB.get())); 

不正確,會導致不確定的行爲。

你在這裏構建的共享指針只需要你給它的指針。它不知道該指針指向的對象已經被另一個智能指針所擁有。此外,它不知道指針指向一個子對象,並且指針將需要被轉換回B*,以便成爲delete d,因爲static_cast將隱藏該信息。

要投射智能指針,您需要能理解並與智能指針集成的演員表。 C++有std::static_pointer_caststd::dynamic_pointer_cast爲此目的。

int main() { 
    std::shared_ptr<B> objectB(new B()); 
    { 
    std::shared_ptr<A> x = std::static_pointer_cast<A>(objectB); 
    std::cout << "End of inner scope\n"; 
    } 
    std::cout << "End of outer scope\n"; 
} 

有了這個代碼程序的輸出顯示正確的行爲:

make A 
make B 
End of inner scope 
End of outer scope 
~B 
~A 

當然,在這個特殊的情況下,你不需要進行明確造型,因爲std::shared_ptr可以找出法律上強制轉換並隱含地做它們。你需要std::static_pointer_cast做雖然向下轉換:

int main() { 
    std::shared_ptr<A> objectA(new B()); 
    { 
    std::shared_ptr<B> x = std::static_pointer_cast<B>(objectA); 
    std::cout << "End of inner scope\n"; 
    } 
    std::cout << "End of outer scope\n"; 
} 

謝謝,最後一兩件事,如果我不需要對象B,它是安全做std::shared_ptr<A> objectA(new B());

是的,這確實是安全的。構造函數shared_ptr<A>收到B*,並且知道足以存儲這樣一個事實,即當發生刪除時,它需要將它所保存的A*轉換爲B*。這確保對象被正確刪除。

但是,如果你真的想你的類型AB表現多態,那麼你應該可能只是通過增加虛擬析構函數讓他們多態類型,然後你不必擔心std::shared_ptr有多聰明是這個。