delegate1-boost::signalを読む

C++C#っぽいdelegateを作ってみよう、というよくあるネタです。
というか、boost::signalですね。
ここBoost.Signals �`���[�g���A��を読むと非常に面白いです。
delegateを実装する際、戻り値の扱いをどうしようか迷っていたのですが、ここに解決策がありました。combiner(統合子)と呼ばれるものです。(ただし、ほとんどのユーザは読む必要がないと書いてある)


boost::signalもかなり規模がデカイので、どこまで実装するかわかりません。
signal/slotという概念もよく知りませんし。
目標はあくまでもdelegateです。
例によって、現状わからない部分は出来るだけカットしつつ、何となくそれっぽいものを作っていきましょう。

C#delegate

いろいろ端折ってありますが、基本的には以下の通りです。

    namespace System
    {
        public delegate void EventHandler(object sender, EventArgs e);
    }

    public class Form
    {
        /*...*/
        public event EventHandler Load;
        /*...*/
    }

    public class Form1 : Form
    {
        private void InitializeComponent()
        {
            /*...*/
            this.Load += new System.EventHandler(this.Form1_Load);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //Form生成時
        }
    }

 +=を使ってEventHandlerに関数を登録して、イベント発生時にまとめて呼び出しているわけですね。

C++で実装

  • functionをstd::listに登録。
  • 戻り値はとりあえず最後に呼ばれたfunctionの戻り値を返す。
template< typename Signature >
class delegate;

//戻り値R、引数2個
template< typename R, typename A1, typename A2 >
class delegate< R ( A1, A2 ) >
{
private:
    std::list< function< R ( A1, A2 ) > > func;

public:
    template< typename F >
    void operator+=( F f )
    {
        func.push_back( f );
    }

    R operator()( A1 a1, A2 a2 )
    {
        while( 最後の直前まで  )
        {
            ( *it )( a1, a2 );
            ++ it;
        }

        return ( *it )( a1, a2 );
    }
};

なんかいろいろカッコ悪いので、boost::signalを見てみましょう。

ただし、boost::signalを使うためにはスタティックライブラリが必要であり、デフォルトでlibファイルがないので、自分でビルドしなければなりません。Let's Boostなどを参考にbjamというツールを使ってコマンドラインからビルドします。

windows vistaの場合、管理者権限で実行しないとビルドが失敗します。私はこれでハマリました。。。

signalN

デフォルトでsignal0〜signal10まであり、signalで選択することも出来ます。

      class signal_base_impl {
      /*...*/
      public:

        // Slots
        mutable named_slot_map slots_;
        any combiner_;

      };

      class signal_base : public noncopyable {
      /*...*/
      public:
        shared_ptr<signal_base_impl> impl;
      };

    // The actual signalN class
    template<
      typename R,
      typename A1, typename A2,
      typename Combiner = last_value<R>,
      typename Group = int,
      typename GroupCompare = std::less<Group>,
      typename SlotFunction = function2< R, A1, A2 >
    >
    class signal2 :
      public signal_base, // management of slot list
      public trackable // signals are trackable
    {
    public:
        R operator()( A1 a1, A2 a2 )
        {
            /*...*/

            // Let the combiner call the slots via a pair of input iterators
            return combiner()(slot_call_iterator(/*...*/), slot_call_iterator(/*...*/));
        }
    };

last_value

boostでは統合子last_valueでslot_call_iterator::operator++()で登録された関数が呼ばれ、slot_call_iterator::operator*()で戻り値を取得しているようです。

  template<typename T>
  struct last_value {
    typedef T result_type;

    template<typename InputIterator>
    T operator()(InputIterator first, InputIterator last) const
    {
      assert(first != last);
      T value = *first++;
      while (first != last)
        value = *first++;
      return value;
    }
  };

  template<>
  struct last_value<void> {
    typedef void result_type;

    template<typename InputIterator>
    result_type
    operator()(InputIterator first, InputIterator last) const
    {
      while (first != last)
        *first++;
      return result_type();
    }
  };


ふむふむ、なるほど。
次回はこれを元にdelegateを書き直しましょう。