17

コンストラクターとデストラクタ内から仮想メソッドを呼び出す際のよく知られた問題により、コンストラクターの直後に呼び出される最終セットアップ メソッドと、それらの直前に呼び出される事前ティアダウン メソッドが必要なクラスになることがよくあります。デストラクタ、次のように:

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++ でこれらのメソッドを自動的に呼び出す方法はありますか? (ないと思いますが、何か賢い方法があれば聞いてみようと思いました)

4

9 に答える 9

11

自動化された方法はありませんが、その型のデストラクタへのユーザー アクセスを拒否し、特別な削除メソッドを宣言することで、ユーザーの操作を強制できます。この方法では、必要な仮想呼び出しを行うことができます。作成は、静的ファクトリ メソッドと同様のアプローチを取ることができます。

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 プライベート メンバーにアクセスできない場合。

于 2009-07-20T05:11:45.797 に答える
3

私が考えることができる最善の方法は、インスタンスを更新して Initialize を呼び出し、そのデストラクタで AboutToDelete を呼び出してから削除する静的な Create メソッドを使用して、独自のスマート ポインターを実装することです。

于 2009-07-20T05:10:49.340 に答える
2

http://www.research.att.com/~bs/wrapper.pdf Stroustrupからのこの論文は、あなたの問題を解決します。

私はこれをVS2008とUBUNTUでg++コンパイラに対してテストしました。それはうまくいきました。

#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);
}
于 2009-07-20T09:35:57.120 に答える
2

非常に慎重に設計されたCreate()ファクトリ メソッド (各クラスの静的メンバー) を使用して、C# が型を初期化するのと同じ順序でコンストラクターとイニシャライザーのペアを呼び出しました。型のインスタンスに a を返し、shared_ptrヒープ割り当てを保証しました。時間の経過とともに信頼性と一貫性が証明されました。

秘訣: XML から C++ クラス宣言を生成しました...

于 2009-07-20T05:10:21.387 に答える
2

破壊前の方法に関する JavedPar のアイデアを除いて、C++ で 2 フェーズの構築/破壊を簡単に行うための事前に作成されたソリューションはありません。これを行う最も明白な方法は、C++ の問題に対する最も一般的な回答: 「間接的なレイヤーを追加する」に従うことです。このクラス階層のオブジェクトを別のオブジェクト内にラップできます。そのオブジェクトのコンストラクタ/デストラクタは、これらのメソッドを呼び出すことができます。たとえば、Couplien のレターエンベロープ イディオムを調べるか、既に提案されているスマート ポインター アプローチを使用します。

于 2009-07-20T07:48:41.347 に答える
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();
}
于 2017-01-04T09:54:45.580 に答える
1

私は同じ問題に悩まされていました.少し調査した後、標準的な解決策はないと思います.

私が最も気に入った提案は、Aleksandrescu et al. で提供されているものです。項目 49 の「C++ コーディング標準」を参照してください。

それらを引用すると(公正使用)、いくつかのオプションがあります。

  1. あなたがしたように、2番目の方法が必要であることを文書化してください。
  2. ポストコンストラクションが行われた場合にフラグを立てる別の内部状態 (ブール値) を持つ
  3. 最も派生したクラスのコンストラクターが使用する基本クラスを決定するという意味で、仮想クラス セマンティクスを使用します。
  4. ファクトリ関数を使用します。

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

于 2012-06-05T08:28:00.427 に答える
0

ポスト コンストラクターを C++ に追加する際の主な問題は、ポスト ポスト コンストラクター、ポスト ポスト ポスト コンストラクターなどを処理する方法がまだ確立されていないことです。

根底にある理論は、オブジェクトには不変条件があるというものです。この不変式は、コンストラクターによって確立されます。確立されると、そのクラスのメソッドを呼び出すことができます。ポスト コンストラクターを必要とする設計の導入により、コンストラクターが実行されるとクラスの不変条件が確立されない状況が発生します。したがって、ポスト コンストラクターからの仮想関数の呼び出しを許可することも同様に安全ではなく、それらが持っているように見えた 1 つの明らかな利点がすぐに失われます。

あなたの例が示すように(おそらくあなたが気付かないうちに)、それらは必要ありません:

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;

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

于 2009-07-20T09:51:59.693 に答える
-1

答えはまだわかりませんが、基本クラスはクラス階層にコードを追加する1つの方法にすぎません。階層の反対側に追加するように設計されたクラスを作成することもできます。

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();
};
于 2009-07-21T07:59:24.977 に答える