2015-12-21 11 views
9

C++ 11を使用して多態性クラスのメンバ関数へのコールバックを格納するマネージャを実装したいと思います。問題は、メンバーが所属するオブジェクトが削除されたり削除されたりする可能性があるため、できるだけシンプルにインターフェイスを作成したい場合はどうすればよいか分かりません。std :: functionとstd :: bindを使用してコールバックを格納し、オブジェクトの削除を処理します。

私は次のことを考えました:オブジェクトにstd::weak_ptrと会員にstd::functionを保存してください。

class MyBase { 
public: 
    MyBase() {} 
    virtual ~MyBase() {} 
}; 
//-------------------------------------------------- 

class MyClass : public MyBase { 
public: 
    MyClass() : MyBase() {} 
    void myDouble(double val) const { std::cout << "Value is: " << val << std::endl; } 
}; 
//-------------------------------------------------- 

Class Manager { 
public: 
    void setFunction(std::weak_ptr<MyBase> base, std::function<void(double)> func) { 
     m_function.first = base; 
     m_function.second = func; 
    } 
private: 
    std::pair<std::weak_ptr<MyBase>, std::function<void(double)>> m_function; 
}; 

はこれを使用するには:以下

動作しているようです

Manager db; 
std::shared_ptr<MyClass> myClass = std::make_shared<MyClass>(); 
db.setFunction(myClass, std::bind(&MyClass::myDouble, myClass, std::placeholders::_1)); 

今、私は彼が唯一の呼び出す必要があるように、利用者からstd::bind一部を非表示にする:

db.setFunction(myClass, &MyClass::myDouble); 

私のマネージャー機能では、ほとんど以下の作業をしたいと考えています。

void setFunc2(std::weak_ptr<MyBase> base, std::function<void(double)> func) { 
    m_function.first = base; 
    m_function.second = std::bind(func, base, std::placeholders::_1); 
} 

しかし、上記のエラーを与える:

error: no match for 'operator=' (operand types are 'std::function<void(double)>' and 
'std::_Bind_helper<false, std::function<void(double)>&, std::weak_ptr<MyBase>&, const std::_Placeholder<1>&>::type {aka std::_Bind<std::function<void(double)>(std::weak_ptr<MyBase>, std::_Placeholder<1>)>}') 
     m_function.second = std::bind(func, base, std::placeholders::_1); 

はこれを行うには良い方法、またはこの作業を取得するために、おそらく方法はありますか?

私が気づいたことは興味深いものです。 std::shared_ptrを使用した場合、use_count()は元のコードでstd::bindへの呼び出しで増分されます。したがって、私は自分のマネージャーのメンバーを解除しない限り、オブジェクトを手動でリセット/破棄することはできません。この動作が文書化されているところでは、私は通常cppreferenceを使用しますか?

私は、次の質問を見てきましたが、それは私の問題のために働くように見えることはできません

How can I use polymorphism with std::function?

+0

ながらリストを変更するときに警告読者は、スレッドの問題を認識しておく必要があり、http://en.cppreference.com/w/cpp/utility/functional/bindは「言いますstd :: bindの戻り値の型は、std :: forwardから構築されたargs ...、[...]のそれぞれに対して1つのオブジェクトを保持します(... (arg_i))。同じページの下に "バインドする引数はコピーまたは移動されます"。あなたがshared_ptr lvalueを渡したので、あなたはコピーを持っています。 – Cubbi

+1

'&MyClass :: myDouble'は、とにかく' std :: function 'に変換されません。 –

+0

@ T.C私はこれを十分に認識しているので、その理由がわかります。私はそのシグネチャがどんなものか気にしません、 'db.setFunction(myClass、MyClass :: myDouble);のような呼び出しをしたいです。 –

答えて

7

テンプレートsetFunctionあなたはポインタ・ツー・メンバーの由来を受け入れ、とそうでないことができるようにcv/ref修飾子の組み合わせに対して12のオーバーロードを記述する必要があります。

template<class D, class D2, class F> 
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) { 
    // optionally static_assert that D2 is a base of D. 
    m_function.first = sp; 
    m_function.second = std::bind(member, sp.get(), std::placeholders::_1); 
} 

は明らかにあなたがlock()m_function.firstm_function.secondを呼び出す前に確認する必要があります。私は、リスナーの実装から私のリスナー/ブロードキャスターをデカップリング好き

std::function<void(double)> m_function; 

template<class D, class D2, class F> 
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) { 
    std::weak_ptr<D> wp = sp; 
    m_function = [wp, member](double d) { 
     if(auto sp = wp.lock()){ 
      ((*sp).*member)(d); 
     } 
     else { 
      // handle pointer no longer valid case. 
     } 
    }; 
} 
+0

幸いにも、コンパイラは' 'random * const 'を' MyBase *が割り当て中です。 'static_assert(std :: is_base_of :: value == true、"オブジェクトは\ MyBase \ 'の派生クラスでなければなりません "); –

+0

'db.setFunction(myClass、nullptr);'を扱うスマートな方法はありますか?この例では理にかなっていませんが、実際のコードでは意味があります。 –

+0

@TheBadger "ハンドル"を定義します。 –

1

また、ちょうどweak_ptrとメンバ関数ポインタの両方をキャプチャラムダを使用しています。

これは、リスナーに要件を設定できないことを意味します。特定の方法でリスナーを割り当てる必要はありません。

私が見つけた最も簡単な方法は、ブロードキャストのライフタイムが接続の存続期間を決定するトークンを返すようにすることです。

using token = std::shared_ptr<void>; 

template<class...Args> 
struct broadcaster { 
    using target = std::function<void(Args...)>; 
    using wp_target = std::weak_ptr<target>; 
    using sp_target = std::shared_ptr<target>; 
    static sp_target wrap_target(target t) { 
    return std::make_shared<target>(std::move(t)); 
    }; 

    token start_to_listen(target f) { 
    auto t = wrap_target(std::move(f)); 
    targets.push_back(t); 
    return t; 
    } 
    void broadcast(Args... args) { 
    targets.erase(
     std::remove_if(targets.begin(), targets.end(), 
     [&](wp_target t)->bool { return t.lock(); } 
    ), 
     targets.end() 
    ); 
    auto targets_copy = targets; // in case targets is modified by listeners 
    for (auto wp : targets_copy) { 
     if (auto sp = wp.lock()) { 
     (*sp)(args...); 
     } 
    } 
    } 
    std::vector<wp_target> targets; 
}; 

これはstd::shared_ptr<void>周りを維持するためにリスナーを登録人々を強制します。

最後にshared_ptr<void>が破棄されても、リストからリスナーが削除された場合は、さらに賢明にすることもできます。しかし、上記の怠惰な登録抹消は私の経験では合理的にうまくいくように思えますし、マルチスレッドに対応するのは比較的簡単です。 (1つの深刻な問題は、ブロードキャストイベントがリスナーのリストに何かを追加したり削除したりするときに起こることです:リスナーがブロードキャストを取得しなかったときにリスナーが追加したルールで、放送は、放送を得ることはありません。リスナーが放送中に同時にを取り出し、私の実装のほとんどで放送を得ることができる...避けるために高価な取得すること。)


我々は代わりに違っそれを切り離すことができます。リスナーはstd::functionstd::weak_ptrを別々にこのブロードキャスタに渡すことができます。同社は両方を保存し、std::weak_ptrが有効な場合はstd::functionを呼び出します。

0

私はヤックのアプローチが好きです。コンパイルに関するいくつかの問題を修正したアップデートされたバージョンがあります(例:関数の名前を登録できません)。また、rm_callbackメソッドを追加して、クライアントが登録トークンをスコープ外に出させたり内部を知ることなく強制的に削除することができます。イベントがブロードキャストされるたびにリストをスキャンするのが好きではなかったので、クリーンアップタスクを実行する共有ポインタにDeleterを追加しました。導入された新しいバグや非効率なものはすべて私のものです。 shared_ptrの使用回数については放送...

using token = std::shared_ptr<void>; 
template<class...Args> 
struct broadcaster { 
    using target = std::function<void(Args...)>; 
    using wp_target = std::weak_ptr<target>; 
    using sp_target = std::shared_ptr<target>; 

    token add_callback(target f) { 
     sp_target t(new target(std::move(f)), [&](target*obj) { delete obj; cleanup(); }); 
     targets.push_back(t); 
     return t; 
    } 

    static void rm_callback(token& t) 
    { 
     t.reset(); 
    } 

    void cleanup() 
    { 
     targets.erase(
      std::remove_if(targets.begin(), targets.end(), 
       [](wp_target t) { return t.expired(); } 
      ), 
      targets.end() 
     ); 
    } 

    void broadcast(Args... args) { 
     for (auto wp : targets) { 
      if (auto sp = wp.lock()) { 
       (*sp)(args...); 
      } 
     } 
    } 

    std::vector<wp_target> targets; 
}; 

// declare event taking a string arg 
broadcaster<std::string> myEvent; 
+1

あなたの 'add_callback'は、' this'を暗黙的に捕捉することによって、放送事業者がリスナートークンよりも長生きすることを前提としています。あなたのラムダがローカルスコープよりも長い場合は、そのような依存関係を隠すので '[&]'を使用しないでください。次に、ブロードキャストリストがリスナーによって変更されたかのように、 'for(:)'ループ不変式が失敗するように、 'broadcast'の' targets'をコピーしたいと思うかもしれません。鉱山にもこれらのエラーの1つがありました。それを私の中に入れた。 – Yakk

関連する問題