2017-09-13 14 views
0

私はVC++ 2015で見ている結果に驚き、どのように動作するか理解するのに役立つ必要があります。unique_ptr <T>のvoid *からT **へのキャストはどのように機能しますか?

struct MyType 
{ 
    MyType(int x_) : x(x_) { } 
    int x; 
}; 

auto u = std::make_unique<MyType>(10); 
void* pv = &u; 

uのアドレスがMyTypeへのポインタではありませんので、これは明らかに失敗します。

MyType *pM = (MyType*)pv; 

しかし、これは動作しますが、pM2uに保存されているMyTypeオブジェクトのアドレスを取得:

MyType** ppM = (MyType**)pv; 
MyType* pM2 = *ppM; 

これは正常に動作するはずのことは、標準規格にはありますか?それとも、私のコンパイラの移植性のない実装の詳細のためだけに動作していますか?何か私はunique_ptrのような方法でラウンドでポインタへのポインタのように扱うことができますか?

そして、あなたが言う前には、「それは愚かだ、void*またはCスタイルのキャストを使用していない」、私はメンバーを構造体へのボイドポインタとオフセットを通じて構造体のシリアライズを扱うレガシーコードで働いていることをご了承ください。私は今、その部分を変更することはできません。しかし、構造体メンバにunique_ptrを使用して、メモリの所有権とクリーンアップを簡素化したいと考えています。そして私はunique_ptrがこのレガシー環境でどれほど壊れやすいか知っています。

+0

あなたは単に 'MyType * pv = u.get();'をしないのですか? http://en.cppreference.com/w/cpp/memory/unique_ptr/get – Brandon

+0

上記のそれぞれについて、*作品ではどういう意味ですか/うまくいかないのですか?それぞれの場合に 'unique_ptr'が所有するオブジェクトへのポインタを取得しようとしていますか?はいの場合、 'MyType ** 'キャストは何ですか? 'void * pv = u.get();'あなたが使っているステートレス・デリゲーターを持つ 'unique_ptr'は、通常、管理対象オブジェクトへのポインタだけを含んでいるので、タイプ・ペニングが機能するようです。 – Praetorian

+0

'std :: unique_ptr'は単一のポインタ属性を持つ構造体である可能性が高いため、' std :: unique_ptr'型のオブジェクトのアドレスは、最初の属性のアドレスと同じであるため、このオブジェクトはポインタです。 – Holt

答えて

2

これは基本的にあなたが幸運になっています。

特定のコンパイラのABIでは、unique_ptrによって管理されているオブジェクトを格納するT*がオブジェクトの最初のメンバーであるため、オブジェクト自体と同じアドレスを持ちます。この例とほとんど同じ方法で:もちろん

struct container { 
    int val; 
}; 

int main() { 
    container c{15}; 

    intptr_t val1 = reinterpret_cast<intptr_t>(&c); 
    intptr_t val2 = reinterpret_cast<intptr_t>(&(c.val)); 

    assert(val1 == val2); //will pretty much always be true 
} 

これはあなたがに依存すべき行動ではありません!標準規格では未定義であり、ベンダーがstd::unique_ptrの中にポインタを格納するためのより良いフォーマットを持っていると判断した場合に変更される可能性があります。

0

これは、一意のポインタが1つのポインタのみをその状態として格納し、その状態がTへのポインタであるため、動作しない未定義の動作です。

未定義の動作は、時間の移動やハードドライブのフォーマットなど、何でも行うことができます。私は人々がこれを言うと他の人はそれが冗談だと​​思うことを知っていますが、これは実際にあなたが実験的に確認できる真実なステートメントです。

ここで定義されていない動作は、「動作する」方法でメモリを再解釈しました。

ライブラリを使用して、非ポッド構造を定義された方法でシリアル化/デシリアライズすることはできません。あなたはそれを動作させるためにハックすることができますが、コンパイラのアップデート(コンパイラフラグのアップデートさえ)は突然完全に異なった動作をする可能性があります。

シリアライズ/デシリアライズ用の構造と、ランタイム用の構造を持つことを検討してください。 1つから他のマーシャル。はい、これは吸う。

+0

私は、ここでの振る舞いは*未定義*ではなく、未定義*であるという印象を受けています。 – Xirema

+0

@ Xiremaそ​​のオブジェクトではないオブジェクトへのポインタを参照解除しますか?これは未定義の動作を引き起こします。特定の一意のptr実装は、 'T * 'と互換性があり、コンパイラの標準ライブラリによって保証されている接頭辞を持つ標準レイアウトにすることができると思います。 – Yakk

1

基本的に、あなたはこのような何かをやっている:いくつかの迂回路とCスタイルのキャストで

std::unique_ptr<MyType> up = ...; 
MyType* p = *reinterpret_cast<MyType**>(&up); 

を。あなたが何らかの理由でこの種のコードを使用しないでください、これは純粋な運と未定義の動作結果であるunique_ptrへのポインタを取るとMyType

のポインタへのポインタとして、それを再解釈します。内部ポインタが必要な場合は、unique_ptrのメソッドget()を使用してください。

+0

あなたは正しいです。 SSCCEをいくつかのオンラインコンパイラでテストしたところ、VS2015とまったく同じように動作しましたが、最終的にはUBであり、避けるべきです。 – Matthew

関連する問題