Frage

Die folgende Rückrufklasse ist ein generischer Wrapper für "Callable Things". Ich mag seine API, die keine Vorlagen hat und sehr sauber ist, aber unter der Motorhaube gibt es eine dynamische Zuweisung, die ich nicht vermeiden konnte.

Gibt es eine Möglichkeit, die loszuwerden? Neu und löschen In dem folgenden Code, während Sie die Semantik und API der Callback -Klasse erhalten? Ich wünschte wirklich, ich könnte.


Benötigte Sachen:

// 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)();
};

Die Rückrufklasse:

// 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;
};

Ein API -Beispiel:

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;
}

Danke vielmals !! :-)

War es hilfreich?

Lösung 2

YAY!!!

Meine beste Lösung, keine Vorlagen, keine dynamische Zuweisung, keine Vererbung (genau wie Union):

#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)();
      };
   };

};

Ok, es ist ein großes hässliches, aber es ist schnell. Für 20 Millionen Iterationen nimmt die Boost -Version 11,8s, Mine mit dynamischem Alloc 9,8s und diese Verwendung von Union 4,2s. Und es ist 60% kleiner als meine unter Verwendung dynamischer Alloc und 130% kleiner als Boost.

Bearbeiten: Aktualisierte Standardkonstruktor.

Andere Tipps

Sie können die Platzierung neu verwenden. Setzen Sie eine maximale Größengrenze, die Sie zulassen möchten callback haben, wie 16 Bytes. Dann setzen Sie eine unsigned char Puffer in dein callback Klasse, das genau so breit ist, und stellen Sie sicher, dass es richtig ausgerichtet ist (GCC hat ein Attribut dafür, und wenn Sie Glück haben, hat Microsoft auch ein Attribut dafür).

Möglicherweise gehen Sie auch ziemlich sicher, wenn Sie eine Gewerkschaft verwenden, und neben dem Char -Puffer die Dummies der Typen, die Sie in sie einfügen möchten, stellt auch eine ordnungsgemäße Ausrichtung sicher.

Verwenden Sie dann, anstatt normale Neue zu verwenden, die Platzierung neu, wie

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);
}

Dann machen Sie das c Mitglied ein Zeiger stattdessen. Sie müssen auch ein Flag einstellen, damit Sie sich daran erinnern, ob Sie im Destruktor das Löschen anrufen müssen, oder den Platzierungspuffer in Ruhe lassen, wenn Sie den Destruktor explizit anrufen.

Das ist Grundsätzlich wie boost::function macht es. Es macht jedoch eine ganze Reihe anderer Dinge, um die Zuteilung zu optimieren. Es verwendet seinen eigenen vtable Mechanismus, um den Raum zu optimieren, und ist natürlich sehr gut getestet.

Dies ist natürlich nicht gerade einfach zu tun. Aber das scheint das einzige zu sein, was man dagegen tun kann.

In Ihrem Beispiel können Sie das Neue entfernen und löschen, indem Sie das entfernen callback Klasse. Dies ist nur ein Dekorateur auf callable_from_object und callable_from_object Das bietet einige syntaktische Zucker: automatisch das richtige Callable -Objekt auszuwählen, an das Sie delegieren können.

Dieser Zucker ist jedoch schön und Sie werden ihn wahrscheinlich behalten wollen. Außerdem müssen Sie die Objekte wahrscheinlich sowieso auf dem Haufen auf dem Haufen platzieren.

Für mich ist die größere Frage, warum Sie eine Rückrufbibliothek erstellen. Wenn es nur C ++ üben soll, ist das in Ordnung, aber es gibt bereits viele Exapel davon:

Warum nicht eine diese verwenden?

Wenn Sie sich auf Ihr Beispiel ansehen, wenn Sie auf dem Weg, den Sie gehen, können Ihre Lösung ohne seine Flexibilität in Richtung Boost :: Funktion konvergieren. Warum also nicht benutzen? Obwohl ich nicht glaube, dass die Boost -Entwickler Götter sind, sind sie sehr überlebende Ingenieure mit einem hervorragenden Peer -Review -Prozess, der sehr starke Bibliotheken führt. Ich glaube nicht, dass die meisten Einzelpersonen oder Organisationen bessere Bibliotheken neu erfinden können.

Wenn Ihr Anliegen eine übermäßige memeorische Zuordnung und Deallokation ist, ist die Lösung dieser Fall wahrscheinlich ein benutzerdefinierter Allocator für die verschiedenen aufrufbaren Subtypen. Aber auch hier bevorzuge ich anderen Menschen, diese Tecniquen zu erforschen und ihre Bibliotheken zu verwenden.

Verwenden Sie Boost :: Funktion und 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;
}

Ja, es funktioniert, aber es ist 20% langsamer als meine Implementierung (mit dynamischer Zuweisung) und Code ist 80% größer.

Ps. Ich wiederholte den Hauptcode 20 Millionen Mal. Die Boost -Version nimmt 11,8s, meine 9,8s.

Bearbeiten:

Sehen Dies

Sie werden nicht in der Lage sein, New Delete als Polymorphismus loszuwerden, und es wird daher versuchen, die Kinderklasse in eine Elternklasse zu kopieren und die Funktionen der Kinderklasse zu verlieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top