3

次のコールバック クラスは、「呼び出し可能なもの」への一般的なラッパーです。テンプレートがなく、非常にクリーンなその API が本当に気に入っていますが、内部には避けられなかった動的割り当てがいくつかあります。

コールバック クラスのセマンティクスと API を維持しながら、以下のコードでnewdeleteを取り除く方法はありますか? 本当にできたらいいのに。


必要なもの:

// base class for something we can "call"
class callable {
  public:
  virtual void operator()() = 0;
  virtual ~callable() {}
};

// wraps pointer-to-members
template<class C>
class callable_from_object : public callable {
  public:
  callable_from_object(C& object, void (C::*method)())
         : o(object), m(method) {}

  void operator()() {
    (&o ->* m) ();
  }
  private:
  C& o;
  void (C::*m)();
};

// wraps pointer-to-functions or pointer-to-static-members
class callable_from_function : public callable {
   public:
   callable_from_function(void (*function)())
         : f(function) {}

   void operator()() {
      f();
   };
   private:
   void (*f)();
};

コールバック クラス:

// generic wrapper for any callable
// this is the only class which is exposed to the user
class callback : public callable {
   public:
   template<class C>
   callback(C& object, void (C::*method)())
         : c(*new callable_from_object<C>(object, method)) {}
   explicit callback(void (*function)())
         : c(*new callable_from_function(function)) {}
   void operator()() {
      c();
   }
   ~callback() {
      std::cout << "dtor\n"; // check for mem leak
      delete &c;
   }
   private:
   callable& c;
};

API の例:

struct X {
  void y() { std::cout << "y\n"; }
  static void z() { std::cout << "z\n"; }
} x;

void w() { std::cout << "w\n"; }

int main(int, char*[]) {
   callback c1(x, &X::y);
   callback c2(X::z);
   callback c3(w);
   c1();
   c2();
   c3();
   return 0;
}

どうもありがとう !!:-)

4

5 に答える 5

5

新しい配置を使用できます。同様に、バイトcallbackなど、許可するサイズの最大制限を設定します。16次に、ちょうどその幅のunsigned charバッファーをクラスに入れ、正しく配置されていることを確認します (そのための属性があり、運が良ければ、microsoft にもそのための属性があります)。callbackGCC

ユニオンを使用すると、かなり安全になる場合もあります。char バッファーの横に、詰め込みたい型のダミーを配置します。これにより、適切なアライメントも保証されます。

次に、通常の new を使用する代わりに、placement new を使用します。

if(placement_allocated< callable_from_object<C> >::value) {
  new ((void*)buffer.p) // union member p is the unsigned char buffer
    callable_from_object<C>(object, method);
  c = (callable*)buffer.p;
} else {
  c = new callable_from_object<C>(object, method);
}

次に、c代わりにメンバーをポインターにします。デストラクタで delete を呼び出す必要があるかどうか、または明示的にデストラクタを呼び出して配置バッファをそのままにしておく必要があるかどうかを覚えておくために、フラグを設定する必要もあります。

それは基本的にどのようboost::functionにそれを行います。ただし、割り当てを最適化するために、他の多くのことを行います。独自の vtable メカニズムを使用してスペースを最適化し、もちろん十分にテストされています。

もちろん、これを行うのは簡単ではありません。しかし、それはそれについて行う唯一のことのようです。

于 2009-08-16T13:27:43.550 に答える
3

わーい!!!

テンプレートを使用せず、動的割り当てを使用せず、継承を使用しない(ユニオンと同じように)私の最善の解決策:

#include <iostream>
#include <stdexcept>

class callback {

   public:

   callback() :
         type(not_a_callback) {}

   template<class C>
   callback(C& object, void (C::*method)()) :
         type(from_object),
         object_ptr(reinterpret_cast<generic*>(&object)),
         method_ptr(reinterpret_cast<void (generic::*) ()>(method)) {}

   template<typename T>
   explicit callback(T function) :
         type(from_function),
         function_ptr((void (*)()) function) {}

   void operator()() {
      switch(type) {
         case from_object:
            (object_ptr ->* method_ptr) ();
            break;
         case from_function:
            function_ptr();
            break;
         default:
            throw std::runtime_error("invalid callback");
      };
   }

   private:

   enum { not_a_callback, from_object, from_function } type;

   class generic;

   union {
      void (*function_ptr)();
      struct {
         generic* object_ptr;
         void (generic::*method_ptr)();
      };
   };

};

わかりました、それは大きく醜いですが、それは速いです。2,000 万回の反復の場合、Boost バージョンは 11.8 秒、動的割り当てを使用する場合は 9.8 秒、ユニオンを使用する場合は 4.2 秒かかります。また、動的割り当てを使用した場合よりも 60% 小さく、boost よりも 130% 小さくなっています。

編集:デフォルトのコンストラクタを更新しました。

于 2009-08-16T14:35:52.593 に答える
1

あなたの例では、callbackクラスを削除することで new と delete を削除できます。これは単なるデコレーターでcallable_from_objectありcallable_from_object、構文糖衣を提供します。デリゲートする正しい呼び出し可能なオブジェクトを自動的に選択します。

しかし、この砂糖は素晴らしいので、おそらくそれを維持したいと思うでしょう. また、おそらく他の呼び出し可能なオブジェクトをヒープに配置する必要があります。

私にとってより大きな疑問は、なぜコールバック ライブラリを作成するのかということです。C++ を練習するだけであれば問題ありませんが、これらの例はすでにたくさんあります。

これらを使用しないのはなぜですか?

あなたの例を見ると、あなたが進んでいる道を歩み続けると、ソリューションは柔軟性のないboost::functionに収束します。では、なぜそれを使用しないのですか?ブースト開発者が神であるとは信じていませんが、彼らは非常に有能なエンジニアであり、優れたピアレビュープロセスを備えているため、非常に強力なライブラリが作成されています. ほとんどの個人や組織が、より優れたライブラリを再発明できるとは思いません。

あなたの懸念が過度のメモリ割り当てと割り当て解除である場合、解決策はおそらくさまざまな呼び出し可能なサブタイプのカスタムアロケーターになるでしょう。しかし、ここでも、他の人がこれらの手法を研究し、ライブラリを使用できるようにしたいと考えています。

于 2009-08-16T13:25:28.700 に答える
1

boost::function と boost:bind を使用します。

typedef boost::function<void ()> callback;

int main(int, char*[]) {
   callback d1 = boost::bind(&X::y, &x);
   callback d2 = &X::z;
   callback d3 = w;
   d1();
   d2();
   d3();
   return 0;
}

はい、動作しますが、私の実装 (動的割り当てを使用) よりも 20% 遅く、コードは 80% 大きくなります。

PS。メインコードを2000万回繰り返しました。ブースト バージョンは 11.8 秒、私のバージョンは 9.8 秒かかります。

編集:

これを見る

于 2009-08-16T14:03:07.967 に答える
0

ポリモーフィズムを行っているため、新しい削除を取り除くことができないため、子クラスを親クラスにコピーして、子クラスの機能を失うことになります。

于 2009-08-16T13:03:50.847 に答える