2
class base {
    int a;
protected:
    template<class T>
    class derived;
public:
    base() {}
    virtual ~base() {}
    virtual void func() {}
    static base* maker();
};

template <class T>    
class base::derived 
    : public base
{
public: 
    derived() {}
    virtual ~derived() {}
    virtual void func() {
        this->~derived(); //<--is this legal?
        new (this) derived<int>(); //<--is this legal?
    }
};

base* base::maker() {
    return new derived<double>();
}

int main() {
    base* p = base::maker(); //p is derivedA<double>
    p->func(); //p is now derivedA<int>
    delete p; //is the compiler allowed to call ~derived<double>()?
}

これは、短く、自己完結型で、正しい(コンパイル可能)、私のコードの例です(これは基本的any_iteratorに私自身の成長のために再発明されています)。

質問は次のようになります。共有ベース上に追加のメンバーがない場合に、同じベースから仮想的に派生した別のタイプで破棄thisおよび再構築するのは未定義の動作ですか?this具体的には、コンパイラーは静的型を追跡する呼び出しを許可されていますか、それとも技術的に不適合ですか?

derivedA[編集]スタック上に作成された場合、コンパイラが誤ったデストラクタを呼び出す可能性があることを指摘する人もいます。(1)コンパイラがそうすることを可能にする標準には何も見つかりません。(2)それは私の質問で意図したこととは別に、スタックに配置 derived できないことを示すようにコードを変更しました。baseただし、スタックに残すことはできます。

4

2 に答える 2

3

明らかにダメだと思います。

序文として、オブジェクトの有効期間はデストラクタを呼び出すことで実際に終了できますが、同じタイプの新しいオブジェクトをその場所に構築することのみが許可されています (そして必須です):

{
  Foo x;
  x.~Foo();
  ::new (&x) Foo;
}  // x.~Foo() must be a valid call here!

スコープの最後でデストラクタが呼び出されることに注意してください。

しかし、あなたの場合、まったく異なるオブジェクトを構築しています:

::new (&x) Bar;   // Bar = DerivedA<int>

明らかにこれsizeof(Bar)を超えたらsizeof(Foo)OKではありません。

(おそらく、オブジェクトのサイズとアライメントの保証について追加の保証を行うことができれば、これについてさらに検討することができます。)

更新: OK、これらの型は同じベースから派生しているので、デストラクタを呼び出すと仮想的な幸福がもたらされると考えていても、それは違反であると確信しています。この静的設定では、コンパイラは仮想呼び出しを静的に解決する可能性があるため、 が指すものの動的型を変更すると、コンパイラの仮定が破られ&xます。

更新 2:同じ問題に関する別の考え: の静的型*&x既知であり、それを尊重する必要があると思います。つまり、ローカル変数の静的な型が変更される可能性をコンパイラが考慮に入れる理由はありません。

于 2011-11-18T00:03:18.197 に答える
1

いくつかの理由から、これは有効なコードではないと確信しています。

  1. 挿入された型が同じサイズでない場合、悪いことが起こります(よくわかりませんが、標準では型のサイズについてあまり約束されていないと思うので、理論的な観点からは難しいかもしれませんそれらが同じサイズであることを証明するため(実際には、おそらくそうなるでしょう))
  2. 変数の型が静的にわかっている場合 (おそらくスタック上に構築されているためですが、理論的には、割り当てを確認してポインターが変更されていないことを証明できれば同じことができます) コンパイラー仮想メソッド呼び出し (デストラクタなど) を自由に静的に解決し、それらを使用すると明らかにコードが壊れる可能性があります
  3. 変数の型が静的に知られていない場合でも、コンパイラーは、変数の存続期間中に型が変更されないと想定できると確信しています (ポインターは関数内で変更できないため、指定されたものを想定できるはずですタイプもそうではありません)。したがって、メソッドを静的に解決することはできませんが、仮想メソッドの以前の呼び出し (型を変更するものなど) からの vmt ポインターを再利用できる可能性があります。

編集:これについて考えてみると、これは厳格なエイリアシング規則に違反していません。新しい配置の後、これは互換性のないタイプを指しているのでしょうか? 関数内で再び明示的にアクセスされることはありませんが、コンパイラによってアクセスが挿入されないことを保証できるとは思いません (可能性は非常に低いですが)。とにかく、これは、コンパイラがこの種のアクションが発生しないと想定できることを意味します。

編集:新しい C++ 標準を見ると、[basic.life] (§3.8.5) が未定義の動作の例と基本的に同じものを提供することがわかりました (実際にはオブジェクトを破壊しませんが、私はそうしません)。それがどのように問題を改善できるかわかりません):

#include<cstdlib>
structB{
    virtual void f();
    void mutate();
    virtual ~B();
};
struct D1:B { void f(); };
struct D2:B { void f(); };
void B::mutate(){
    new(this)D2; //reuses storage—ends the lifetime of *this
    f(); //undefined behavior
    ...=this; //OK, this points to valid memory
}
void g(){
    void* p = std::malloc(sizeof(D1) + sizeof(D2));
    B* pb = new(p)D1;
    pb->mutate();
    &pb; //OK: pb points to valid memory
    void* q = pb; //OK: pb points to valid memory
    pb->f(); //undefined behavior, lifetime of *pb hasended
}

これは、これが許可されていないことを証明する必要があります。

于 2011-11-18T02:33:47.087 に答える