2016-09-21 18 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た型にダウンキャストしなければなりません。例えば

std::shared_ptr

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::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には実際にはオブジェクトへのポインタが格納されていませんが、実際のオブジェクト、参照カウンタ、およびdeleterを保持する中間構造体へのポインタが格納されています。 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の設計の完成を伝える正解をupvoteします。 +1 :) –

+0

@RichardHodges私は 'std :: unique_ptr'を彼らがオーバーヘッドの点で自由であるので好む。答えは、良い仕事@rodrigo、それはほとんどすべてをカバーしています。 –

+1

'shared_ptr'は' shared_ptr :: get() 'から返されたポインタを保持します。しかし、これはDeleterに渡されるポインタと同じではありません。 –

4

shared_ptrは常に削除手段に元のポインタではなく、1を渡しますvptr.get()によって得られます。これは、このケースを機能させるだけでなく、コンストラクタのオーバーロードshared_ptr<T>::shared_ptr(const shared_ptr<T>&, element_type*)に実装されているメンバーのサブオブジェクトおよび所有オブジェクトへのポインタを持つためにも必要です。

これは安全です。

関連する問題