2017-02-10 9 views
2

基本クラスに初期化ファイナライザを実装したいと思います。最も簡単な方法は、ファイナライズ手順をメソッドに提供することです。残念なことに、最も派生したコンストラクタの終わりに常にそれを呼び出すことを忘れないように強制します。私は理想的な効果を達成するために、基本クラスでRAIIを使用することができると思って、このようなコードを書いた:C++で初期化ファイナライザを実装する方法は?

#include <iostream> 
using namespace std; 

struct Base 
{ 
    Base() 
    { 
     struct Finishializer 
     { 
      ~Finishializer() 
      { 
       cout << "Base::~Finishializer" << endl; 
      } 
     } finishializer; 
     cout << "Base::Base()" << endl; 
    } 

    ~Base() 
    { 
     cout << "Base::~Base()" << endl; 
    } 
}; 

struct Derived : Base 
{ 
    Derived() 
    { 
     cout << "Derived::Derived()" << endl; 
    } 

    ~Derived() 
    { 
     cout << "Derived::~Derived()" << endl; 
    } 
}; 

int main() 
{ 
    Derived(); 
} 

それから私は、コンストラクタのためのコールスタック評価の逆の順序があり実現しました。 私の "Finishializerパターン"は、まずベースコンストラクタをコールし、ベースコンストラクタの終わりに(次のスタックフレームとして)派生コンストラクタをコールする必要があります。 残念ながら、C++(私の場合はVS2015)は他の方法で動作します。 これは派生コンストラクタを呼び出しますが、最初の命令として(次のスタックフレームとして)ベースコンストラクタを呼び出します。

それは次のような出力につながる:代わりに私の夢を見た1の

Base::Base() 
Base::~Finishializer 
Derived::Derived() 
Derived::~Derived() 
Base::~Base() 

Base::Base() 
Derived::Derived() 
Base::~Finishializer 
Derived::~Derived() 
Base::~Base() 

がそのままスタックを評価するためにいくつかの良い理由がある、または多分それは私のように変更することができます"finishializers"を実装するように提案?現在のC++実装でこのようなことをするための代替パターンを知っていますか?

+1

何Factoryパターンの使用について? – user463035818

+3

良いスタートはコンストラクタの構造体* out *を移動し、 'finishializer'を標準のメンバ変数にすることです。 –

+0

また、 '' final'デストラクタに "finishializer"コードを置くのは間違っていますか?それはデストラクタの目的です*。あなたはこれのユースケースを詳しく教えていただけますか?この解決策が解決しなければならない問題は何ですか? –

答えて

1

私はあなたが基本クラスで何かを使ってやりたいことをする方法を見つけませんでした。私が見つけた唯一のことは、最も派生したクラスのコンストラクタにコードを追加することです。あなたのケースでは、

Derived::Derived() 
{ 
    cout << "Derived::Derived()" << endl; 
    Finishializer(); 
} 

とすぐにDerivedから派生して、新たに派生クラスでFinishializer();のコードを行使したいことを決定するなどのメンテナンスの頭痛の種であること。繰り返しになりますが、私は、最も派生したクラスに明示的にコードを追加しなくても動作するデザイン/実装パターンに遭遇していません。

+0

これは私が現時点で(ただし、基本メソッドを呼び出すことによって)扱う方法ですが、最も派生した各コンストラクタで特別な呼び出しについて覚えておく必要があります。 – Piotrek

+0

@Piotrek、賢明なプログラマーがあなたが望むように機能するテクニックを見つけてくれることを祈ってみましょう。もしそうでなければ、あなたは言語の限界に縛られて生きなければならないでしょう。 –

+0

私は建設中に考えたようにコールスタックが評価されないのはなぜかと思います。おそらくいくつかの理由があるかもしれませんが、私はそれを見つけることができません。オリジナルの問題は、そのような「仕上げ剤」が非常に便利な最初の問題ではありません。 – Piotrek

1

以下の最初のアプローチは、ファクトリ関数を使用して、少し汚いですが、私はそれがあなたが望むことをすると思います。

もっと馬鹿にならないもう1つの方法は、具体的な使用可能なクラスに対して特別な最終クラスの導出を要求することです。例えば。このアプローチは、MicrosoftのATLライブラリでは使用されますが、強制はありません。しかし、のは、最初の工場をやらせる:

#include <iostream> 
#include <memory>  // std::unique_ptr  
#include <type_traits> // std::is_base_of 
#include <utility>  // std::(forward) 
using namespace std; 

void say(char const* const s) { cout << s << "\n"; } 

#define STATIC_ASSERT(e) static_assert(e, "`" #e "` <-- must hold.") 

template< class Base, class Derived > 
using Is_base_and_derived = is_base_of<Base, Derived>;  // (sic!). 

class Base 
{ 
friend class Base_factory; 
private: 
    void finalize_construction() { say("Finalizing construction."); } 

protected: 
    virtual ~Base() { say("Base::<destroy>"); } 
    Base() { say("Base::<init>"); } 
}; 

class Derived: 
    public Base 
{ 
friend class Base_factory; 
protected: 
    ~Derived() override { say("Derived::<destroy>"); } 
    Derived() { say("Derived::<init>"); } 
}; 

class Base_factory 
{ 
private: 
    static void destroy(Base const* const p) 
    { delete p; } 

public: 
    template< class Class > 
    using Ptr_ = unique_ptr<Class, void(*)(Base const*)>; 

    template< class Class, class... Args > 
    static auto make_a(Args&&... args) 
     -> Ptr_<Class> 
    { 
     STATIC_ASSERT((Is_base_and_derived<Base, Class>::value)); 
     auto result = Ptr_<Class>{ 
      new Class{ forward<Args>(args)... }, &destroy 
      }; 
     result->finalize_construction(); 
     return result; 
    } 
}; 

auto main() 
    -> int 
{ 
    (void) Base_factory::make_a<Derived>(); 
} 

強制最も派生クラスのアプローチは、上記のコードでの動的割り当てを回避し、それがクライアントコードの適合性への依存を避ける:

#include <assert.h>  // assert  
#include <iostream> 
#include <memory>  // std::unique_ptr  
#include <type_traits> // std::is_base_of 
#include <utility>  // std::(forward) 
using namespace std; 

void say(char const* const s) { cout << s << "\n"; } 

#define STATIC_ASSERT(e) static_assert(e, "`" #e "` <-- must hold.") 


//------------------------------------------- Machinery: 

class With_enforced_most_derived; 

template< class Some_class, class The_virtual_top = With_enforced_most_derived> 
class Construction_finalizer_ final 
    : public Some_class 
    , public virtual The_virtual_top 
{ 
public: 
    template< class... Args > 
    Construction_finalizer_(Args&&... args) 
     : The_virtual_top{ typename The_virtual_top::Key{} } 
     , Some_class{ forward<Args>(args)... } 
    { say("Finalizing construction."); } 
}; 

class With_enforced_most_derived 
{ 
template< class, class > friend class Construction_finalizer_; 
private: 
    enum Key{}; 

public: 
    With_enforced_most_derived() // Needed for intermediate classes. 
    { assert("Most derived class must be `Construction_finalizer_`" && 0); } 

    With_enforced_most_derived(Key) 
    {} 
}; 

//------------------------------------------- Example usage: 

class Base 
    : public virtual With_enforced_most_derived 
{ 
public: 
    virtual ~Base() { say("Base::<destroy>"); } 
    Base() { say("Base::<init>"); } 
}; 

class Derived: 
    public Base 
{ 
public: 
    ~Derived() override { say("Derived::<destroy>"); } 
    Derived() { say("Derived::<init>"); } 
}; 


auto main() 
    -> int 
{ 
#ifdef CRASH_BABY_CRASH 
    (void) Derived{}; 
#endif 
    (void) Construction_finalizer_<Derived>{}; 
} 
+0

これはそれを取り巻く方法の1つです。問題はclentはまだいくつかの特別な知識を持っている必要があります - この場合、派生したクラスで友人であり、新しいオブジェクト(make_sharedもスタック割り当てもありません)を作成する非標準的な方法です。私はまだコンストラクタのスタックがベースから派生したものではないということは疑問です。 – Piotrek

+0

@Piotrek:まあ、あなたは特別な最も派生したクラスのものを使うことができます。強制的に行うには、仮想トップレベルベースの友情を使って行うことができますが、それには少しオーバーヘッドがあります。そのアプローチの良い点は、クライアントコードのために間違って行く方法がないということです。 –

+1

@Piotrek:私は例を追加しました。 –

関連する問題