Seite 1 von 2
Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 9:14 am
von Glocke
Hiho, ich arbeite mal wieder an meienm Netzwerk-Framework weiter und habe (vereinfacht) folgende Situation:
Code: Alles auswählen
#include <iostream>
#include <map>
typedef int CommandID;
class Base;
typedef void (Base::*CallbackPtr)(const std::string& data);
class Base {
protected:
std::map<CommandID, CallbackPtr> callbacks;
public:
void trigger(CommandID id, const std::string& data) {
auto entry = this->callbacks.find(id);
if (entry == this->callbacks.end()) {
std::cout << "Undefined callback for #" << id << std::endl;
} else {
CallbackPtr callback = entry->second;
(this->*callback)(data);
}
}
};
// ----------------------------------------------------------------------------
CommandID FOO = 1;
CommandID BAR = 2;
class Derived: public Base {
protected:
void foo(const std::string& data) {
std::cout << "foo: " << data << std::endl;
}
void bar(const std::string& data) {
std::cout << "bar: " << data << std::endl;
}
public:
Derived(): Base() {
this->callbacks[FOO] = (CallbackPtr)(&Derived::foo);
this->callbacks[BAR] = (CallbackPtr)(&Derived::bar);
}
};
// ----------------------------------------------------------------------------
int main() {
Derived d;
d.trigger(FOO, "test");
d.trigger(BAR, "1337");
}
Dabei gehört der oberste Teil ins Framework, der mittlere in die Implementierung (wenn ich das Framework verwende) und das letzte ist eine andere Form der Anwendung.
Zum Hintergrund: In meinem Framework werden JSON-Objekte serialisiert, versandt und deserialisiert. Um den Objekten Callback-Methoden zuzuweisen, will ich IDs verwenden und quasi die Callback-Methode, die zur CommandID hinterlegt wurde, mit den übrigen JSON-Daten aufzurufen.
Auf was ich hinaus möchte: Kann ich das ganze noch weiter "verschönern", oder bin ich da (ohne ausgefallene OOP-Design Pattern) am Ende der (einfachen) Fahnenstange?
LG Glocke
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 9:44 am
von Xin
Glocke hat geschrieben:Hiho, ich arbeite mal wieder an meienm Netzwerk-Framework weiter und habe (vereinfacht) folgende Situation:
Code: Alles auswählen
this->callbacks[FOO] = (CallbackPtr)(&Derived::foo);
this->callbacks[BAR] = (CallbackPtr)(&Derived::bar);
Auf was ich hinaus möchte: Kann ich das ganze noch weiter "verschönern", oder bin ich da (ohne ausgefallene OOP-Design Pattern) am Ende der (einfachen) Fahnenstange?
Ohne jetzt alles genau verfolgt zu haben - der Quelltext ist auf einem Level, wo mir die Fehler nicht mehr so ins Auge stechen und die Komplexität schlichte Schönheit nicht mehr erlaubt. Aber es sieht doch soweit anständig aus.
Das einzige, was mir zunächst auffiel ist: Warum castest Du? Muss das sein? Ein Cast ist häufig der Hinweis "Hier hat einer was versaut".
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 9:48 am
von Glocke
Xin hat geschrieben:Das einzige, was mir zunächst auffiel ist: Warum castest Du? Muss das sein? Ein Cast ist häufig der Hinweis "Hier hat einer was versaut".
Weil ein Zeiger auf eine Base-Methode kein Zeiger auf eine Derived-Methode ist. Eine andere Lösung habe ich bisher nicht gefunden. Ohne Cast:
test.cpp:40:39: Fehler: »void (Derived::*)(const string&) {aka void (Derived::*)(const std::basic_string<char>&)}« kann nicht nach »std::map<int, void (Base::*)(const std::basic_string<char>&)>::mapped_type {aka void (Base::*)(const std::basic_string<char>&)}« in Zuweisung umgewandelt werden
Wenn ich die map in Base deklariere, kenne ich ja die von Base abgeleiteten Klassen (und damit die entsprechenden Pointer) nicht. Die map aber in die Ableitungen auszugliedern, hals dem "Framework-Anwender" Arbeit auf, die ich im Framework halten möchte.
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 10:00 am
von Xin
Glocke hat geschrieben:Xin hat geschrieben:Das einzige, was mir zunächst auffiel ist: Warum castest Du? Muss das sein? Ein Cast ist häufig der Hinweis "Hier hat einer was versaut".
Weil ein Zeiger auf eine Base-Methode kein Zeiger auf eine Derived-Methode ist. Eine andere Lösung habe ich bisher nicht gefunden. Ohne Cast:
test.cpp:40:39: Fehler: »void (Derived::*)(const string&) {aka void (Derived::*)(const std::basic_string<char>&)}« kann nicht nach »std::map<int, void (Base::*)(const std::basic_string<char>&)>::mapped_type {aka void (Base::*)(const std::basic_string<char>&)}« in Zuweisung umgewandelt werden
Ah, jetzt ja, eine Insel. ^^
Nun seh' ich's auch.
Womit er auch vollkommen recht hat (ergo Du Mist gebaut hast). Du kannst nun Derived::foo() mit einem Base-Objekt aufrufen, wobei Derived::foo() aber auch auf Derived::Member zugreifen kann, dass ein Base-Objekt nicht hätte: Segmentation Fault.
Der Cast ist immer dann notwendig, wenn ein Hack passiert - die Sache kann natürlich dann sicher sein, wenn garantiert ist, dass Derived::Foo() und Derived::Bar() niemals auf Member zugreifen, die nicht in Base verfügbar sind. dann könnten Foo() und Bar() aber auch z.B. protected-Methods von Base sein!?
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 10:03 am
von Glocke
Xin hat geschrieben:Womit er auch vollkommen recht hat (ergo Du Mist gebaut hast). Du kannst nun Derived::foo() mit einem Base-Objekt aufrufen, wobei Derived::foo() aber auch auf Derived::Member zugreifen kann, dass ein Base-Objekt nicht hätte: Segmentation Fault.
Verdammt xD Wie löse ich das Problem? Dem Methoden-Zeiger könnte ich (wenn ich den direkt in der Base-Klasse typedeffe) ein T verpassen, so dass ich Derived von Base<Derived> ableite - dazu aber Derived nochmal vorher prototypen muss... Dann hätte ich in meiner Base-Ableitung "Derived" eine Map mit Derived-Methodenzeigern... Ist das mit dem Template-Parameter overkill? Mir erscheint es so -.-'
/EDIT: Ich bekomme nur keinen Segmentation Fault, wenn ich aus der Derived::foo (nachdem sie via Base:: Methodenzeiger aufgerufen wurde) und auf Derived's "float f" zugreifen will. das Klappt!
Code: Alles auswählen
#include <iostream>
#include <map>
typedef int CommandID;
class Base;
typedef void (Base::*CallbackPtr)(const std::string& data);
class Base {
protected:
std::map<CommandID, CallbackPtr> callbacks;
public:
void trigger(CommandID id, const std::string& data) {
auto entry = this->callbacks.find(id);
if (entry == this->callbacks.end()) {
std::cout << "Undefined callback for #" << id << std::endl;
} else {
CallbackPtr callback = entry->second;
(this->*callback)(data);
}
}
};
// ----------------------------------------------------------------------------
CommandID FOO = 1;
CommandID BAR = 2;
class Derived: public Base {
private:
float f;
protected:
void foo(const std::string& data) {
std::cout << this->f << data << std::endl;
}
void bar(const std::string& data) {
std::cout << "bar: " << data << std::endl;
}
public:
Derived(): Base() {
this->f = 1.3f;
this->callbacks[FOO] = (CallbackPtr)(&Derived::foo);
this->callbacks[BAR] = (CallbackPtr)(&Derived::bar);
}
};
// ----------------------------------------------------------------------------
int main() {
Derived d;
d.trigger(FOO, "test");
d.trigger(BAR, "1337");
}
Aber ich gebe dir Recht: der Type-Cast ist nicht schön.
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 10:15 am
von Xin
Glocke hat geschrieben:/EDIT: Ich bekomme nur keinen Segmentation Fault, wenn ich aus der Derived::foo (nachdem sie via Base:: Methodenzeiger aufgerufen wurde) und auf Derived's "float f" zugreifen will. das Klappt!
Du hast ja auch ein derived-Objekt.
Das weiß die base::trigger-Methode aber nicht... nun ist es nicht so, dass es zufällig klappt, denn da Du "Base::callbacks" in Derived::Derived() konfigurierst - aber der Compiler kann es innerhalb von base::trigger() nicht prüfen, sondern muss den (möglichen) Fehler melden. Es ist ein Hack, der nur funktioniert, wenn Du dem Compiler mit dem Cast den Mund verbietest und erklärst "Ich weiß schon, was ich hier tue!".
Ich halte die Lösung mit dem Template für besser. Sie ist nicht nötig, aber ich halte sie nicht für einen Overkill. Ich halte sie für sauberer.
Du musst selbst entscheiden, ob Du Dir den Code mit einem Template verkomplizieren möchtest, um einen Cast zu beseitigen, der (in genau diesem Beispiel) keine fehlerhafte Situation erzeugen kann - sofern die Maps nicht zum Beispiel zwischen Base-Objekten ausgetauscht werden können und auf einmal ein Derived2-Objekt auf Member von Derived-Objekten zugreifen wollen, die sie nicht haben.
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 10:21 am
von Glocke
Xin hat geschrieben:Du musst selbst entscheiden, ob Du Dir den Code mit einem Template verkomplizieren möchtest, um einen Cast zu beseitigen, der (in genau diesem Beispiel) keine fehlerhafte Situation erzeugen kann - sofern die Maps nicht zum Beispiel zwischen Base-Objekten ausgetauscht werden können und auf einmal ein Derived2-Objekt auf Member von Derived-Objekten zugreifen wollen, die sie nicht haben.
Sicherlich wäre das die saubere Lösung

Allerdings müsste dadurch die Implementierung der Template-Klasse komplett in den Header rutschen - Die Implementierung von Methoden von Template-Klassen ist afaik nur in Headern möglich. Das würde die ganze Sache imho abartig hässlich zu lesen und zu erweitern machen.
Die Maps bleiben beim jeweiligen Derived-Objekt. Genau genommen ist die Base-Klasse meine grundlegende Server- bzw. Client-Klasse und die Derived sind jeweils Ableitungen des Servers (class ChatServer: public Server), der den Server erweitert und eben die Callbacks für die Commands realisiert. I.d.R. existiert pro laufender Anwendung genau ein Server bzw. ein Client (ein Server und ein Client könnten parallel existieren, unterscheiden sich aber bzgl. der Callbackfunktion, da der Server-Callback noch die ID des Clients, von dem die Daten kamen, enthält).
Jetzt weiß ich nicht, was ich machen soll

Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 10:46 am
von Xin
Glocke hat geschrieben:Sicherlich wäre das die saubere Lösung

Allerdings müsste dadurch die Implementierung der Template-Klasse komplett in den Header rutschen - Die Implementierung von Methoden von Template-Klassen ist afaik nur in Headern möglich. Das würde die ganze Sache imho abartig hässlich zu lesen und zu erweitern machen.
Die Basisklasse, die Derived ist ja keine Templateklasse, sondern erbt nur von dieser. Deren Implementierung kann also normal stattfinden.
Glocke hat geschrieben:Jetzt weiß ich nicht, was ich machen soll

Ausprobieren und Erfahrung sammeln.
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 11:22 am
von Glocke
Xin hat geschrieben:Die Basisklasse, die Derived ist ja keine Templateklasse, sondern erbt nur von dieser. Deren Implementierung kann also normal stattfinden.
Ich meine auch die Implementierung der Basis-Klasse

Ich geh die Sache mal an ^^
Re: Methodenzeiger abgeleiteter Klassen
Verfasst: Mi Apr 17, 2013 11:45 am
von Xin
Glocke hat geschrieben:Xin hat geschrieben:Ich meine auch die Implementierung der Basis-Klasse

Alles, was nicht vom Template-Parameter abhängt kannst Du in eine Basisklasse von Base verlagern und normal implementieren.