2009-07-20 11 views
9

コンストラクタとデストラクタの内部から仮想メソッドを呼び出すことでよく知られている問題のため、私は最終的に、コンストラクタの直後に呼び出されるfinal-setupメソッドを必要とするクラスと、ちょうど呼ばれるプリティアードメソッドそのデストラクタ前に、次のように:ポストコンストラクタとプリデストラクタ仮想メソッド呼び出しを実装する自動化された方法はありますか?

MyObject * obj = new MyObject; 
obj->Initialize(); // virtual method call, required after ctor for (obj) to run properly 
[...] 
obj->AboutToDelete(); // virtual method call, required before dtor for (obj) to clean up properly 
delete obj; 

これは動作しますが、それはそれで呼び出し側が適切な時期に、これらの方法のいずれかまたは両方を呼び出すことを忘れてしまうリスクを運びます。

これは問題です:C++でこれらのメソッドを自動的に呼び出す方法はありますか?呼び出し元は呼び出しを覚えておく必要はありませんか? (私はそこにないと推測していますが、とにかく賢明な方法がある場合には、とにかく頼むと思っていました)

+0

デストラクタにはどのような問題がありますか? – peterchen

+4

実際の問題を説明する必要があるかもしれません。これらの呼び出しは実際には*必要ではないかもしれません。 – peterchen

+2

ctorsまたはdtorsから仮想メソッドを "一般的に"呼び出す必要がある場合、大きな設計上の問題があるようです。これが必要なクラスの例を挙げることはできますか?ほとんどの場合、より簡単な解決策があります。 (通常どおり、私はRAIIが問題を解決することを期待しています。自分自身のctors/dtorsがそれぞれ独自の初期化/ティアダウンの部分を使って、1つ以上のメンバ変数に問題を委譲してください) – jalf

答えて

1

C++にポストコンストラクタを追加することの主な問題は、誰もがまだなど

ポストポストコンストラクタ、ポストポストポストコンストラクタ、根底にある理論に対処する方法を確立していないということですオブジェクトには不変量があるということです。この不変量は、コンストラクタによって設定されます。それが確立されると、そのクラスのメソッドを呼び出すことができます。ポストコンストラクタを必要とするデザインが導入されると、コンストラクタが実行されるとクラス不変条件が成立しない状況が発生します。したがって、ポストコンストラクタからの仮想関数への呼び出しを許可することは同様に安全ではなく、直面していたような明白な利点をただちに失うことになります。あなたの例が示すように

(おそらくあなたが実現せず)、それらは必要ありませんしている:

MyObject * obj = new MyObject; 
obj->Initialize(); // virtual method call, required after ctor for (obj) to run properly 

obj->AboutToDelete(); // virtual method call, required before dtor for (obj) to clean up properly 
delete obj; 

は、これらのメソッドが必要とされていない理由のはをお見せしましょう。これら2つの呼び出しは、MyObjectまたはその拠点の1つから仮想関数を呼び出すことができます。ただし、MyObject::MyObject()でもこれらの関数を安全に呼び出すことができます。 MyObject::MyObject()の返品後に何も起こらず、obj->Initialize()を安全にします。したがってobj->Initialize()が間違っているか、その電話をMyObject::MyObject()に移動することができます。同じ論理がobj->AboutToDelete()の逆に適用されます。最も派生したデストラクタが最初に実行され、それでもAboutToDelete()を含むすべての仮想関数を呼び出すことができます。

+5

Initialize()がMyObjectのサブクラスで再実装されている場合を除き、MyObject :: Initialize()ではなくサブクラスの実装を呼び出す必要があります。 MyObjectのコンストラクタから呼び出され、必要な処理を実行しません。 (AboutToDelete()はMyObject ::〜MyObject()から呼び出されると同じ問題を抱えます) とにかく、「MyObject :: MyObject()が返された後に起きること」は、サブクラスのコンストラクタの実行です。 Initialize()が実行される前に発生します。このロジックは、サブクラスのデストラクタが実行される前に実行する必要があるAboutToDelete()に対して逆になります。 –

+0

これは明らかに 'new MyObject'が呼び出しの直前にあるので、ここでは当てはまりません。あなたの反例は単に名前を変えるだけです。最も派生したコンストラクタが最後に実行され、すべての不変条件が成立し、すべての仮想関数を呼び出すことができます。そのコンストラクタは引き続きInitialize()を呼び出すことができます – MSalters

+4

最も派生したコンストラクタは、最も派生したコンストラクタであることを確実に知ることができないため、Initialize()を安全に呼び出すことができません。別のクラスがそれをサブクラス化している可能性があります。その場合、Initialize()はあまりにも早く呼び出されます。 –

2

非常に慎重に設計されたCreate()ファクトリメソッドC#が型を初期化するのと同じ順序でコンストラクタと初期化子のペアを呼び出す。タイプのインスタンスにshared_ptrを返し、ヒープ割り当てを保証しました。信頼性が高く、一貫性があることが証明されました。

トリック:私は私のC++ XMLからのクラス宣言...

+0

私はpre-destructionロジックへの呼び出しを含む 'shared_ptr'にカスタムディレターを提供していると仮定していますか? –

3

独自のスマートポインタを実装するために、私は初期化を呼び出すニュースアップインスタンスをし、静的Createメソッドであると考えることができる最高の生成そのデストラクタでAboutToDeleteを呼び出して削除します。

9

自動化された方法はありませんが、そのタイプのデストラクタへのユーザーのアクセスを拒否し、特別な削除メソッドを宣言することによって、ユーザーを強制することができます。この方法では、あなたが望むバーチャルコールを行うことができます。作成は静的なファクトリメソッドと同様のアプローチをとることができます。

class MyObject { 
    ... 
public: 
    static MyObject* Create() { 
    MyObject* pObject = new MyObject(); 
    pObject->Initialize(); 
    return pObject; 
    } 
    Delete() { 
    this->AboutToDelete(); 
    delete this; 
    } 
private: 
    MyObject() { ... } 
    virtual ~MyObject() { ... } 
}; 

これで、「delete obj;」を呼び出すことはできません。コールサイトがMyObjectプライベートメンバーにアクセスできる場合を除きます。

+0

私はそれが好きです、+1! –

+1

デストラクタは仮想であることができます(そして、そうすべきです)、なぜ余分な作業を行うのですか? –

+0

@dribeas、それを仮想化するように更新しました。 – JaredPar

1

事前破壊法に関するJavedParの考えを除いて、C++で2段階の構築/破壊を容易に行うための既成の解決策はありません。これを行うための最も明白な方法は、C++で最もよくある問題への回答に従うことです。「間接参照の別のレイヤを追加する」 このクラス階層のオブジェクトを別のオブジェクトにラップすることができます。そのオブジェクトのコンストラクタ/デストラクタは、これらのメソッドを呼び出すことができます。たとえば、Couplienの手紙の封筒のイディオムを調べるか、すでに提案されているスマートポインタのアプローチを使用します。

2

http://www.research.att.com/~bs/wrapper.pdfこのペーパーはStroustrupの問題を解決します。

私はVS 2008の下でこれをg ++コンパイラに対してUBUNTUでテストしました。それは正常に働いた。

#include <iostream> 

using namespace std; 

template<class T> 

class Wrap 
{ 
    typedef int (T::*Method)(); 
    T* p; 
    Method _m; 
public: 
    Wrap(T*pp, Method m): p(pp), _m(m) { (p->*_m)(); } 
    ~Wrap() { delete p; } 
}; 

class X 
{ 
public: 
    typedef int (*Method)(); 
    virtual int suffix() 
    { 
     cout << "X::suffix\n"; 
     return 1; 
    } 

    virtual void prefix() 
    { 
     cout << "X::prefix\n"; 
    } 

    X() { cout << "X created\n"; } 

    virtual ~X() { prefix(); cout << "X destroyed\n"; } 

}; 

class Y : public X 
{ 
public: 
    Y() : X() { cout << "Y created\n"; } 
    ~Y() { prefix(); cout << "Y destroyed\n"; } 
    void prefix() 
    { 
     cout << "Y::prefix\n"; 
    } 

    int suffix() 
    { 
     cout << "Y::suffix\n"; 
     return 1; 
    } 
}; 

int main() 
{ 
    Wrap<X> xx(new X, &X::suffix); 
    Wrap<X>yy(new Y, &X::suffix); 
} 
+0

ではなくprotectedと宣言しなければならない場合、しかし、これはコンストラクタとデストラクタではない標準的なメソッドだけをラップするようです。 – iain

0

まだ答えは見当たりませんが、基本クラスはクラス階層にコードを追加する唯一の方法です。私は同じ問題で立ち往生して

template<typename Base> 
class Derived : public Base { 
    // You'd need C++0x to solve the forwarding problem correctly. 
    Derived() : Base() { 
     Initialize(); 
    } 
    template<typename T> 
    Derived(T const& t): Base(t) { 
     Initialize(); 
    } 
    //etc 
private: 
    Initialize(); 
}; 
1

、および研究のビットの後、私は、任意の標準液が存在しないと信じて:あなたはまた、階層の他の側に付加されるように設計されたクラスを作成することができます。

私が一番好きだった提案は、Aleksandrescu et al。ちょうどあなたが行ったようにあなたは、第二の方法が必要であること、それを文書化

  1. :彼ら(公正使用)を引用項目49.

    に書籍「コーディング標準C++は」、あなたはいくつかのオプションがあります。

  2. は、ポストの建設は、ほとんどの派生クラスのコンストラクターが決定したという意味で、場所に
  3. 使用する仮想クラスのセマンティクスを取った場合、フラグは、基本クラス
  4. 使用を使用するには、別の内部状態(boolean)を持っていますファクトリ関数。

詳しくは、彼の本を参照してください。

1

クラス内で静的関数テンプレートを使用できます。プライベートctor/dtorで。 vs2015コミュニティで実行

class A { 
    protected: 
    A() {} 
     virtual ~A() {} 
     virtual void onNew() = 0; 
     virtual void onDelete() = 0; 
    public: 

     void destroy() { 
      onDelete(); 
      delete this; 
     } 

     template <class T> static T* create() { 
      static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A"); 
      T* t = new T(); 
      t->onNew(); 
      return t; 
     } 
    }; 

class B: public A { 
    friend A; 

    protected: 
      B() {} 
      virtual ~B() {} 

      virtual void onNew() override { 
      } 

      virtual void onDelete() override { 
      } 
}; 

int main() { 
    B* b; 
    b = A::create<B>(); 
    b->destroy(); 
} 
関連する問題