[C++] generische Callbacks bzw. einsetzbare Funktionen

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Schrompf
Moderator
Beiträge: 4934
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

[C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Schrompf »

Hallo Leute,

ich ringe hier gerade mit einer Art Callback-Situation, für die es sicher schon eine generisch freundliche Lösung in C++ oder Boost gibt. Folgendes Szenario:

Code: Alles auswählen

template <class Behandler>
struct Tester
{
  /// evtl. Konstruktor
  Tester();
  /// startet die Prüfung ausgehend von dem gegebenen Ding
  void Pruefen( const Irgendwas* ding);

  /// Behandler, der im Treffer-Fall aufgerufen wird
  Behandler behandler;
};
Der Sinn soll soll, dass der Tester ausgehend von dem übergebenen Ding durch irgendwelche Datensätze rappelt und alle Objekte prüft, die ihm begegnen. Für alle Objekte, die eine bestimmte Testbedingung erfüllen, soll ein Callback aufgerufen werden. In AnsiC wäre das ein Kandidat für Funktionszeiger. Aber die möchte ich gern vermeiden. Zudem wäre es schön, die Möglichkeit zu haben, dem Behandler auch einen State mitgeben zu können, wenn man es braucht. Der Tester im obigen Beispiel bekommt also die Behandlerklasse als Template-Parameter und erstellt sich davon einen Member. Für jedes Ding, dass den Test besteht, wird nun eine Funktion des Behandlers aufgerufen. Ein Behandler, der alle bestandenen Objekte sammelt, köntne also z.B. so aussehen:

Code: Alles auswählen

struct SammelBehandler
{
  /// die Funktion, die bei erfolgreichem Test aufgerufen wird
  void BeiErfolg( const Irgendwas* ding)
  {
    mGefundeneDinge.push_back( ding);
  }

  /// alle Dinge, die den Test bestanden haben
  std::vector<Irgendwas*> mDinge;
};
Diese Klasse könnte man jetzt als Templateparameter in obigen Tester einsetzen und bekommt ein Array mit allen Dingen, die den Test bestanden haben. Der "Callback" wäre also die Funktion BeiErfolg(). Nun möchte ich mir aber alle Wege für die Zukunft offen halten. Ich weiß z.B., dass es std::unary_function und sowas gibt, das solche Callback-Strukturen über den operator() abbildet. Außerdem gibt es da boost::bind(), mit dem man alle möglichen Callbacks sowie auch das oben skizzierte Szenario abbilden könnte. Und ganz am Ende möchte ich mir auch die Option offenhalten, mit den C++0x-Closures arbeiten zu können, wenn ich nur ein bisschen "Einmal-Code Bei Testerfolg" brauche. Wie muss nun der Syntax der Tester-Klasse aussehen, so dass ich beliebige Callbacks aller Art einsetzen kann? Welchen Typ hat boost::bind, so dass ich tatsächlich Plain Functions, struct::operator() und Klassenmethoden-Callbacks gleichermaßen einsetzen kann? Wie muss der Callback aussehen, damit ich C++0x-Closures einsetzen kann? Und wie erhalte ich mir bei all diesen Überlegungen noch die Inline-Performance, die ich durch die CompileTime-Lösung des Callbacks bekommen habe?

Ich habe hier gerade keine Idee, wonach ich noch suchen könnte, um ein praktisch freundliches Beispiel zu finden. Könnt ihr mir da helfen?

bye, Thomas
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Sternmull »

Hast du mal einen Blick auf das Tutorial in der Doku von boost.function geworfen? Dort werden die Fragen eigentlich beantwortet. Dort steht auch gleich als erstes unter "Basic Usage" welchen Typ die Funktionsobjekte haben und wie man ihnen (Member-)Funktionen und Funktionsobjekte zuweist.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4934
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Schrompf »

Naja, boost::function ist ja ein gekapselter Callback-Typ. Der betreibt einigen Aufwand hinter den Kulissen, soweit ich das verstanden habe.

Ich habe jetzt einfach mal die Definition von std::for_each() und Artverwandten angeschaut. Und siehe da: die nehmen einfach nur einen komplett abstrakten Template-Parameter als Callback. Damit kompiliert natürlich alles, was sich mit einem operator () und der passenden Funktionsignatur benutzen lässt. Damit funktionieren zumindest schonmal banale Funktionen, Funktoren und über boost::bind auch Member Function Callbacks. So in der Art:

Code: Alles auswählen

template <typename CallFunc>
void DoSomethingOnEach( const std::vector<Bla*>& stuff, const CallFunc& callFunc)
{
  for( Bla* bla : stuff )
  {
    callFunc( bla);
  }
}
Ruft den übergebenen Callback für jedes Element aus dem Vektor auf. Über boost::bind auch Klassenmethoden

Code: Alles auswählen

  DoSomethingOnEach( stuff, boost::bind( &Class::Method, this, _1));
Nur mit den C++0x-Lambda Expressions muss ich es noch erproben.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4934
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Schrompf »

Hm, es ist doch alles nicht so einfach wie erhofft. Die oben vorgestellte Lösung funktioniert erwartungsgemäß mit Funktoren mit Zustand nicht. Boost::bind nimmt den "Callback" per Value:

Code: Alles auswählen

template <typename Callback> void DoSomething( Container& container , Callback callback);
Diese Lösung kompiliert allzeit. Allerdings wird das Callback-Ding als Kopie übergeben. Wenn man also vorher eine Instanz davon erzeugt, der Funktion übergibt und danach den Status des Dings abfragen will, liest man immer Unsinn, weil die Aufrufe alle an eine Kopie davon gingen. Und mit der offiziellen Empfehlung über boost::ref kompiliert es nicht, weil der reference_wrapper<T> natürlich keinen operator() anbietet.

Meine ursprüngliche Lösung als const reference geht auch nicht, weil da der Callback-Functor natürlich const ist und demzufolge der non-const operator() gar nicht aufgerufen werden kann. Und als non-const reference geht es zwar alles prima, aber Visual Studio warnt bei Übergabe eines temporären Objekts wie z.B. dem von boost::bind() zu Recht
Visual C++ 2010 hat geschrieben:warning C4239: Nicht dem Standard entsprechende Erweiterung: 'Argument': Konvertierung von 'boost::_bi::bind_t<R,F,L>' in 'boost::_bi::bind_t<R,F,L> &'
Was mich wieder an den Anfang der Frage bringt: wie implementiert ihr Eure Callbacks? Oder falls das Wort "Callback" zu sehr nach altem C klingt, können wir es auch Algorithmen-Teilspezialisierung oder Function Plugin nennen. Es muss doch eine saubere und bequeme Lösung dafür geben, spezialisierten Code in dafür vorgesehene Stellen eines Algorithmus einzufügen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Aramis
Moderator
Beiträge: 1458
Registriert: 25.02.2009, 19:50
Echter Name: Alexander Gessler
Wohnort: 2016
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Aramis »

Notfalls - falls es wirklich absolut generisch sein muss - kannst du auch noch eine weitere Indirektion einfuehren:

Code: Alles auswählen

class Base {

public:
	virtual void operator() ();
};


template <class T>
class ImplementationA : public Base
{
public:
	MyImplementation(const T* self, (void T::*callback)())
		:self(self)
		,callback(callback)
	{}

	void operator() () {
		self->*callback();
	}	


	T* self;
	void T::*callback();
};

// .. weitere Implementierungen, z.b. fuer boost::bind, std::function's (der Typ eines C++0x lambdas)



Base* MakeCallback(const T* self, (void T::*callback)() ) {
	...
}

// weitere Ueberladungen um auto type deduction auszunuetzen.



void Foo::FooBar() {
	DoSomething(container,MakeCallback(this,&Foo::Bar));
}
Schlussendlich schreibst du damit teilweise boost.bind nach, aber mit einem zusaetzlichen Level an Indirektion und maximal viel Flexibilitaet. Auch stateful-Zeugs ist damit kein Problem. Um die Zahl der Parameter an den Callback flexibel zu halten, kann auch die Basisklasse noch ein Template werden. In etwa so:

Code: Alles auswählen


class None {};
template <typename P1 = None, typename P2 = None, typename P3 = None, ...usw> class Base;
template <typename P1, typename P2> class Base <P1,P2,None> {
public:
	virtual void operator() (P1,P2);
};

template <typename P1> class Base <P1,None,None> {
public:
	virtual void operator() (P1);
};

template <> class Base <None,None,None> {
public:
	virtual void operator() (P1);
};

// usw ... kann man ggf. auch mit C++0x' Variadic Templates loesen.


Wird damit dann etwas unschoen, bietet aber maximale Flexibilitaet.
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Sternmull »

Da ist doch aber auch ein virtueller Funktionsaufruf dabei. Und wenn Schrompf nicht zwingend auf seine Inlineperformance angewiesen ist (was ich zugegebenermaßen bei meiner ersten Antwort übersehen hatte), dann würde ich der Einfachheit halber bei meinem Vorschlag boost.function bleiben. Ich sehe keine Grund für eine Hausgemachte implementierung von etwas das durch boost bereits zur Verfügung gestellt wird. Außerdem würde mich mal interessieren ob die "Inlinebarkeits-Anforderung" überhaupt sinnvoll ist. Haben boost.function bzw. der virtuelle Funktionsaufruf denn wirklich solchen Einfluss auf die Performance das sie als Lösungen ausgeschlossen werden müssen? Details zum Einsatzszenario fehlen ja leider.
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von kimmi »

Ich benutze schlicht Funktoren.

Gruß Kimmi
Benutzeravatar
Schrompf
Moderator
Beiträge: 4934
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Schrompf »

Inline ist nicht wirklich Pflicht.... wär schön, muss aber nicht. Immerhin arbeitet der Szene-Iterator ja auch rekursiv, da verbietet sich inline ja genauso. Ich hatte aber auch mehr eine allgemeine Diskussion im Sinn, was für Möglichkeiten es gibt und in welchen Bereichen sie sich anbieten.

Ich habs jetzt über die non-const reference gelöst. Das ist zwar ne rein-VC-Lösung, aber das reicht mir erstmal.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8295
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Krishty »

Schrompf hat geschrieben:Ich habs jetzt über die non-const reference gelöst. Das ist zwar ne rein-VC-Lösung, aber das reicht mir erstmal.
Wäre es möglich, die durch eine rvalue Reference zu ersetzen, damit das wieder portabel wird? (Habe ich jedenfalls in einem meiner Projekte so gemacht)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4934
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Schrompf »

Wie rvalue reference? Ich möchte ja explizit keine Kopie anlegen, sondern die orginale Strukturinstanz verwenden. Oder ich verstehe nicht, was Du meinst. Hast Du Beispielcode zur Syntax-Anschauung?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von kimmi »

Da wäre ich auch neugierig. Ich habe zwar schon viel von rvalues gelesen, konnte mir aber selber bisher kein Bild davon machen.

Gruß Kimmi
Benutzeravatar
Krishty
Establishment
Beiträge: 8295
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] generische Callbacks bzw. einsetzbare Funktionen

Beitrag von Krishty »

Ich habe keine Lösung zu deinem ursprünglichen Problem; ich weiß nur, wie man die Warnung wegkriegt.
Schrompf hat geschrieben:Wie rvalue reference? Ich möchte ja explizit keine Kopie anlegen, sondern die orginale Strukturinstanz verwenden. Oder ich verstehe nicht, was Du meinst. Hast Du Beispielcode zur Syntax-Anschauung?
Wenn ich mich recht irre, ging es um diese Zeile:

Code: Alles auswählen

template <typename Callback> void DoSomething( Container& container , Callback callback);
Dort möchtest du als zweiten Parameter eine Referenz (Callback &) akzeptieren, damit das Objekt selber manipuliert wird, und nicht eine temporäre Kopie davon. Das geht, solange das übergebene Callback-Objekt eine benannte, adressierbare Variable ist. Ist es hingegen ein temporäres Objekt – lies: rvalue – ist es nicht mehr portabel, weil Visual C++ zwar das Binden von rvalues an lvalue-Referenzen erlaubt, aber zurecht warnt, dass es nicht standardkonformes Verhalten ist.

Der Sinn dahinter ist: Wenn dort eine nicht-const-deklarierte Referenz zu einem Objekt steht, dann wollte der Programmierer, dass nach der Ausführung eine Wirkung im Objekt hinterlassen wird und der Aufruf mit einem temporären Objekt wäre mit hoher Wahrscheinlichkeit Missbrauch der Funktionalität.

Da du temporäre Objekte per rvalue-Referenz erkennen und von dort an auch adressieren kannst, wäre die Lösung, die Funktion für rvalues zu überladen:

Code: Alles auswählen

// Deine jetzige Funktion:
template <typename Callback> void DoSomething( Container& container , Callback & callback);

// Die Überladung für temporäre Callback-Objekte: Du bringst hiermit zum Ausdruck, dass die Übergabe
//    eines temporären Objekts keinen Missbrauch der Funktion darstellt sondern von dir einkalkuliert
//    und behandelt wurde.
template <typename Callback> void DoSomething( Container& container , Callback && callback) {
    // Solange nicht explizit gecastet, verhält sich jede rvalue-Referenz wie eine normale Referenz, der
    //    Typ des Ausdrucks "callback" entspricht hier also "Callback &" und passt wie gespuckt auf deine
    //    ursprüngliche Funktion.
    return DoSomething(container, callback);
}
Hoffe, das hilft.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten