Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Callback Strategies

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 < [ 2 ]
000
23.09.2003, 17:58 Uhr
~Hötzi
Gast


Hi,

Let's assume we have two classes OWNER and MEMBER. MEMBER is a member of OWNER. It's common that OWNER calls functions of it's MEMBER instance. But what about the other way round. Let's say MEMBER has a callback function, that is triggered by some global timer. And MEMBER wants to inform it's OWNER about that.

Approach 1: MEMBER has a pointer to OWNER that is initialized at construction time. There are two problems with this approach.

1.) circular dependencies.
2.) MEMBER must know the OWNER. What if I want to use MEMBER with different owner classes?


Approach 2: OWNER passes a function-pointer to MEMBER, that will be called.
Problems: In c++ you can't use pointers to member-functions, at least I wasn't successful here. You have to invent a workaround with global functions. One global function for each instance...



Approach 3: I create a Callback-Class, with a virtual callback-function. The OWNER inherits from this Class and overwrites the Callback Function to his needs. The OWNER passes a baseclass-pointer of the callback-class to MEMBER and MEMBER calls the overwritten function. Of course OWNER mustn't inhert directly, but can use a member-class that inherits from the Callback-Class.

This seems to be the "cleanest" approach to me. The MEMBER dosen't has to know anything about the OWNER, because it just deals with the Callback-Baseclass-pointer. So you can use different OWNER-classes. Global-namespace is not polluted and you don't have to mess around with function pointers.

Nevertheless, I think it's quite complicated. I just want communication, not only from OWNER to MEMBER, but also from MEMBER to OWNER. Does anyone know a simpler approach? Or do you avoid those situations at all?



Greetings,
Markus
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
23.09.2003, 18:52 Uhr
0xdeadbeef
Gott
(Operator)


Zum 1. Ansatz:
Ich sehe das Problem mit zirkulären Referenzen nicht. Solange du keinen schlecht programmierten Garbage Collector benutzt, dürftest du damit keine Probleme haben. Was den zweiten Punkt angeht - taken, aber in den meisten Fällen haben die möglichen Owner für den Member, wenn man Callback-Funktionen benutzt (was meistens dann passiert, wenn die Dinger eigentlich eine Einheit bilden), zumindest eine ähnliche Struktur. Beispielsweise werden GUI-Controls meistens in Dialogen oder zumindest entsprechenden Containerklassen verwendet, ein gut durchdachter Klassenbaum löst hier die meisten Probleme.

Wenn der erste Ansatz nicht machbar ist - naja, den zweiten halte ich für nicht sonderlich gelungen. Man kann pointer auf Member-Funktionen durch die Gegend reichen, aber idR versaut man durch sowas den Code. Sinnvoller ist Ansatz 3, der ziemlich laut "FUNKTOREN" schreit. Funktoren sind im wesentlichen Klassen, die den operator() überladen, und die dementsprechend wie normale Funktionen aufgerufen werden können. Dadurch, dass es Objekte sind, kannst du die spezielle Funktionalität, die gecallbackt wird, wunderbar kapseln. Ein etwas widerliches Beispiel:

C++:
class basis_funktor {
public:
  void operator() = 0;
}

class owner;

class konkreter_funktor {
protected:
  owner *parent;
public:
  konkreter_funktor(owner*);
  void operator();
}

class member {
protected:
  konkreter_funktor &callback;
public:
  member(konkreter_funktor&);
  void trigger_callback();
}

class owner
{
protected:
  member mb;
  konkreter_funktor called_back_fktr;
  int i;
public:
  owner();
}

konkreter_funktor::konkreter_funktor(owner *p) : parent(p) { }
void konkreter_funktor::operator()() { parent->i++; }

member::member(konkreter_funktor& cb) : callback(cb) { }
void member::trigger_callback() { callback(); } //führt calback.operator()() aus.

owner::owner() : i(0), called_back_fktr(this), mb(called_back_fktr) { }


--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra

Dieser Post wurde am 23.09.2003 um 18:52 Uhr von 0xdeadbeef editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
23.09.2003, 19:23 Uhr
virtual
Sexiest Bit alive
(Operator)


Hallo,
bzgl. Ansatz 1:
Ich habe zwar keine Ahnung, was das mit einem garbagecollector zu tun haben soll, den es ohnehin nicht in C++ gibt, aber dieser Ansatz ist nicht unüblich und wird wie folgt gelöst:

C++:
/* member.h */
class Owner; // Forwarddecl. von Klasse Owner.

class Member
{
    Owner* m_poOwner;
}


In member.h braucht Owner nicht includiert zu werden, in der passenden .cpp Datei wird man das schon tun. Dieser Ansatz ist dann nicht möglich, wenn man Owner nicht als Pointer oder Referenz in Member verwenden möchte oder wenn Owner eine Templatespezialiserung ist (weil es in diesem Fall nicht möglich ist, eine Forwarddeklaration zu machen, std::string is ein Beispiel dafür).

Ansatz 2/3:
Zu diesem Zweck gibt es funktoren, wie beefy richtig erwähnt. Schau die mal die STL template familie mem_fun_t an, die kommt sogar klar, wenn der Callback virtuell ist.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
23.09.2003, 19:54 Uhr
0xdeadbeef
Gott
(Operator)


Es gibt Garbage-Kollektoren für C++. Die verwenden dann nicht new und delete, aber es gibt sie. Und wenn man jetzt zufällig einen verwendet, der nur auf Referenzzählung beruht... (Gut, das wird schon seit den siebzigern nicht mehr gemacht, aber...)
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
23.09.2003, 19:56 Uhr
virtual
Sexiest Bit alive
(Operator)


Was einem garbagecollector derzeit wohl am nächsten kommt, sind Smartpointer und auch hier sehe ich keine Relation zur Fragestellung.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
005
23.09.2003, 20:30 Uhr
~(un)wissender
Gast


Sagt mal, hat in beefs Beipiel eigentlich die Klasse basis_funktor irgendeinen Sinn?
Ist es sicher eine Referenz (konkreter_funktor &callback) als Attribut einer Klasse zu nutzen?
Ich meine, was ist, wenn ich aus einer Funktion diesen Konstruktor aufrufe und das Objekt auf das die Referenz zeigt ungültig wird (durch verlassen der Funktion), auf was zeigt dann die Referenz(konkreter_funktor &callback)?
Oder wird auch bei konkreter_funktor &callback das Objekt kopiert (eigentlich nicht, oder?).
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
006
23.09.2003, 20:36 Uhr
~(un)wissender
Gast


@beffy
Ich glaube in class owner musst du die Reihenfolge von
member mb;
konkreter_funktor called_back_fktr;
vertauschen, da sonst die initialisierung nicht klappt.
mb setzt das Objekt called_back_fktr voraus und das ist noch nicht vorhanden.
Ich kann mich auch irren, korregiert mich dann bitte!
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
007
23.09.2003, 21:45 Uhr
0xdeadbeef
Gott
(Operator)


OK, ok. Asche über mein Haupt, ich hab in der Eile den Code verpfuscht. Hier richtig:

C++:
class basis_funktor {
public:
  virtual void operator() = 0;
}

class owner;

class konkreter_funktor : public basis_funktor {
protected:
  owner *parent;
public:
  konkreter_funktor(owner*);
  virtual void operator();
}

class member {
protected:
  basis_funktor &callback;
public:
  member(basis_funktor&);
  void trigger_callback();
}

class owner
{
protected:
  member mb;
  konkreter_funktor called_back_fktr;
  int i;
public:
  owner();
}

konkreter_funktor::konkreter_funktor(owner *p) : parent(p) { }
void konkreter_funktor::operator()() { parent->i++; }

member::member(basis_funktor& cb) : callback(cb) { }
void member::trigger_callback() { callback(); } //führt calback.operator()() aus.

owner::owner() : i(0), called_back_fktr(this), mb(called_back_fktr) { }


Was die Initialisierungsreihenfolge in owner angeht - ich initialisiere called_back_fktr vor mb. Schau mal den Konstruktor an.

@virtual: www.hpl.hp.com/personal/Hans_Boehm/gc/
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra

Dieser Post wurde am 23.09.2003 um 21:48 Uhr von 0xdeadbeef editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
008
23.09.2003, 22:17 Uhr
~(un)wissender
Gast


Ja, aber ich glaube, man muss die Initialisierung der Attribute in der exakten Reihenfolge vornehmen, zumindest behauptet das Scott Meyers und der hat eigentlich Ahnung.
Wenn du called_back_fktr erstellt, dann ist schon mb erstellt.
Du hast also zumindest einen Konstuktoraufruf zuviel, im schlechten Falle sogar falschen Code.
Ich bin jetzt zu faul das auszuprobieren, vielleicht morgen.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
009
23.09.2003, 23:30 Uhr
0xdeadbeef
Gott
(Operator)


Hm. Also, in der Praxis haut das auf jeden Fall hin, weil ich ja eine Referenz (also nachher auf der Maschine eine Adresse) durch die Gegend reiche. Ich bin allerdings nicht sicher, was der Standard dazu meint. virtual?

Wie dem auch sei, der Code war ja nun nicht dazu gedacht, wirklich benutzt zu werden, sondern sollte lediglich das Prinzip verdeutlichen.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 < [ 2 ]     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: