2016-09-21 20 views
3

共享指針非常聰明。他們記得他們最初構建的類型,以便正確刪除它們。採取這一例如:可否將shared_ptr <T>上傳到shared_ptr <void>會導致未定義的行爲?

struct A { virtual void test() = 0; }; 
struct B : A { void test() override {} }; 

void someFunc() { 
    std::shared_ptr<A> ptr1; 

    ptr1 = std::make_shared<B>(); 

    // Here at the end of the scope, B is deleted correctly 
} 

然而,似乎是空指針問題:一個垂頭喪氣的一個空指針是有效的,就必須將其向下轉換它最初是從upcasted類型。

例如:

void* myB = new B; 

// Okay, well defined 
doStuff(static_cast<B*>(myB)); 

// uh oh, not good! 
// For the same instance of a child object, a pointer to the base and 
// a pointer to the child can be differrent. 
doStuff(static_cast<A*>(myB)); 

隨着std::shared_ptr,當您使用std::make_shared,在缺失者必須類似於這樣的功能:[](B* ptr){ delete ptr; }。由於指針(在第一個示例中)在指向A的指針中保存B實例並將其正確刪除,因此必須以某種方式對其進行向下轉換。

我的問題是:下面的代碼片段調用未定義的行爲?

void someFunc() { 
    { 
     std::shared_ptr<void> ptr = std::make_shared<B>(); 

     // Deleting the pointer seems okay to me, 
     // the shared_ptr knows that a B was originally allocated with a B and 
     // will send the void pointer to the deleter that's delete a B. 
    } 

    std::shared_ptr<void> vptr; 

    { 
     std::shared_ptr<A> ptr = std::make_shared<B>(); 

     // ptr is pointing to the base, which can be 
     // different address than the pointer to the child. 

     // assigning the pointer to the base to the void pointer. 
     // according to my current knowledge of void pointers, 
     // any future use of the pointer must cast it to a A* or end up in UB. 
     vptr = ptr; 
    } 

    // is the pointer deleted correctly or it tries to 
    // cast the void pointer to a B pointer without downcasting to a A* first? 
    // Or is it well defined and std::shared_ptr uses some dark magic unknown to me? 
} 

回答

5

該代碼是正確的。

std::shared_ptr內部保存真實指針和真正的刪除者,因爲它們在構造函數中,所以無論你如何向下轉發,只要向下轉換有效,刪除器就會正確。

shared_ptr實際上並不包含指向對象的指針,而是指向保存實際對象,引用計數器和刪除器的中間結構的指針。不管你是否投shared_ptr,該中間結構不會改變。它不能改變,因爲你的vptrptr儘管是不同的類型,但共享引用計數器(當然還有對象和刪除器)。

順便說一下,該中間結構是make_shared優化的原因:它將中間結構和對象本身都分配在同一個內存塊中,並避免了額外的分配。

爲了說明指針有多聰明就可以了,我已經寫了,因爲你的問題與普通指針的程序崩潰(使用GCC 6.2.1):

#include <memory> 
#include <iostream> 

struct A 
{ 
    int a; 
    A() :a(1) {} 
    ~A() 
    { 
     std::cout << "~A " << a << std::endl; 
    } 
}; 

struct X 
{ 
    int x; 
    X() :x(3) {} 
    ~X() 
    { 
     std::cout << "~X " << x << std::endl; 
    } 
}; 

struct B : X, A 
{ 
    int b; 
    B() : b(2) {} 
    ~B() 
    { 
     std::cout << "~B " << b << std::endl; 
    } 
}; 

int main() 
{ 
    A* a = new B; 
    void * v = a; 
    delete (B*)v; //crash! 

    return 0;  
} 

其實它打印了錯誤的整數值,其中證明了UB。

~B 0 
~A 2 
~X 1 
*** Error in `./a.out': free(): invalid pointer: 0x0000000001629c24 *** 

但隨着智能指針的版本工作得很好:

int main() 
{ 
    std::shared_ptr<void> vptr; 

    { 
     std::shared_ptr<A> ptr = std::make_shared<B>(); 
     vptr = ptr; 
    } 
    return 0; 
} 

它打印效果與預期:

~B 2 
~A 1 
~X 3 
+0

我會一直恭維一個正確答案,表達了shared_ptr的完美設計。 +1 :) –

+0

@RichardHodges我喜歡'std :: unique_ptr',因爲它們在開銷方面是免費的;)。至於答案,好@rodrigo,它幾乎涵蓋了一切。 –

+1

'shared_ptr' *確實包含一個指針,一個從'shared_ptr :: get()'返回的指針。但是這不是傳遞給刪除者的指針。 –

4

shared_ptr始終原來指針傳遞給缺失者,而不是一個通過vptr.get()獲得。這不僅是爲了使這種情況起作用,而且還有指向成員子對象和擁有對象的指針,如構造函數過載shared_ptr<T>::shared_ptr(const shared_ptr<T>&, element_type*)中所體現的。

所以這是安全的。

相關問題