2

例から始めましょう:

#include <cstdio>

struct Base { virtual ~Base() {} virtual void foo() = 0; };
struct P: Base { virtual void foo() override { std::printf("Hello, World!"); } };
struct N: Base { virtual void foo() override {} };

void magic(Base& b);
// Example implementation that changes the dynamic type
// {
//     void* s = dynamic_cast<void*>(&b);
//     b.~B();
//     new (s) N();
// }

int main() {
    std::aligned_storage<sizeof(P), alignof(P)> storage;
    void* s = static_cast<void*>(storage);

    new (s) P();

    Base& b = *static_cast<Base*>(s);

    magic(b);

    b.foo();
 }

標準によると、何をb.foo()印刷する必要がありますか?

個人的な意見: でbインスタンスを破棄した後に古くなったため、未定義magicです。この場合、 で置き換えるb.foo()static_cast<B*>(s)->foo()合法になりますか?


合法である (または合法ではない) 例が得られたので、私たち標準主義者全員が直面しているより一般的な問題は、オブジェクトの動的な型を変更することが許可されているかどうかです。C++ コンパイラが (幸いなことに) ストレージを再利用する可能性があることは既にわかっているため、少し注意が必要です。

質問は理論的なように思えるかもしれませんが、コンパイラーに直接適用できます。コンパイラーはb.foo()上記b.P::foo()のプログラムで非仮想化できますか?

したがって、私は探しています:

  • 私自身の小さなプログラムに関する明確な答え (私は思いつきませんでした)。
  • オブジェクトの動的な型を変更する合法的な方法の可能な例 (単一で十分です)。
4

2 に答える 2

3

規格の§8.5.3.2によると、初期化後に参照を別のオブジェクトにバインドすることはできません。配置newによって新しいオブジェクトが作成されるため、そのルールに違反し、未定義の動作が発生します。

オブジェクトの動的タイプは変更できません。あなたの例でも、オブジェクトのタイプを変更するのではなく、古いオブジェクトと同じ場所に新しい別のオブジェクトを作成します。あなたがそれについて考えるならば、オブジェクトの動的タイプを変更することは、余分なデータメンバーに対応するためにオブジェクトをインプレースでサイズ変更し、VMTを変更することを意味します(そしてそれは他のオブジェクトを移動し、ポインターを台無しにします...)言語の規則の範囲内で行われる。

于 2012-10-04T17:56:00.350 に答える
0

これは未定義の動作です。あなたのmagic例は、参照のセマンティクスに違反しています。

また、ダウンdynamic_castキャスト用です。キャスト先は.void*static_cast

質問に明確に答えるには:

  • コンパイラは、実行時の型を証明できる場合、好きな関数呼び出しを「非仮想化」することがあります。
  • 参照がそれが参照するオブジェクトよりも長生きする場合、それは UB です。
  • オブジェクトの動的な型を変更することはできません。最も近い方法は、ポインターを再割り当てすることです。

    Base * ptr;
    P p;
    N n;
    
    ptr = &p; ptr -> foo ();
    ptr = &n; ptr -> foo ();
    

ただしp、 andnは、範囲外になるまで (または、ヒープに割り当てられている場合は、deleted になるまで) 型が固定されます。

于 2012-10-04T17:44:59.980 に答える