| Autor |
Beitrag |
Aya
      
Beiträge: 1964
Erhaltene Danke: 15
MacOSX 10.6.7
Xcode / C++
|
Verfasst: Mi 27.05.09 21:53
Hi,
ich hab eine frage zu C++... ich habe folgendes konstrukt:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| class Parameters { public: int a; };
class SimpleParameters : public Parameters { public: int b; void print() { cio << a << " .. " << b << cinl; } };
typedef void (*SimpleEvent)(Parameters*);
void doSomething(SimpleParameters *p) { p->print(); }
[......]
SimpleEvent ev = (SimpleEvent)doSomething; SimpleParameters param; param.a = 23; param.b = 42; ev(¶m); |
Was ich da tue ist, ich definiere ein Callback für eine funktion ohne rückgabe wert (void) und einem Parameter vom typ "Parameter*". Die funktion die ich der callback-variable im endeffekt dann zuweise hat aber als Parameter "SimpleParamters*", sprich eine von Parameters abgeleitete klasse.
Damit ich das ganze dem Callback zuweisen kann muß ich es typecasten.
Die frage ist jetzt, kann ich das problemlos machen?
Es funktioniert zwar jetzt in dem beispiel, aber bevor ichd as jetzt fleißig einbaue und alles darauf umstelle würde ich gern sichergehen das es auch wirklich korrekter code ist - und das es nicht nur grad zufällig mit dem simplen beispiel funktioniert
Aya~
_________________ Aya
I aim for my endless dreams and I know they will come true!
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Do 28.05.09 08:47
Es mag jetzt in deinem einfachen Falle funktionieren, aber korrekt ist es nicht.
Das Problem wo du reinläufst ist, wenn jemand wirklich einmal die Minimal-Forderung deines Callbacks (nämlich die im Datentyp) übergibt und daher bestimmte Felder undefiniert sind. Bei abgeleiteten Klassen bleibt zumindest die interne Struktur der Vorgänger-Klasse erhalten, so dass jegliche Zugriffe auf den minimal vereinbarten Typ garantiert funktionieren.
Das Verhalten für dein jetziges Beispiel ist von daher undefiniert, scheint aber für deinen speziellen Sonderfall (Aufruf mit einer Subkasse des deklarierten Datentyps äquivalent zur aufzurufenden Callback-Methode) zu funktionieren.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Aya 
      
Beiträge: 1964
Erhaltene Danke: 15
MacOSX 10.6.7
Xcode / C++
|
Verfasst: Do 28.05.09 10:13
Hi, BenBE hat folgendes geschrieben : | | Das Problem wo du reinläufst ist, wenn jemand wirklich einmal die Minimal-Forderung deines Callbacks (nämlich die im Datentyp) übergibt und daher bestimmte Felder undefiniert sind. Bei abgeleiteten Klassen bleibt zumindest die interne Struktur der Vorgänger-Klasse erhalten, so dass jegliche Zugriffe auf den minimal vereinbarten Typ garantiert funktionieren. |
Klar, das es knallt wenn ich nur eine Variable vom typ "Parameters" übergebe und dann versuche dieses mit der Print-methode zu printen ist klar.
BenBE hat folgendes geschrieben : | | Das Verhalten für dein jetziges Beispiel ist von daher undefiniert, scheint aber für deinen speziellen Sonderfall (Aufruf mit einer Subkasse des deklarierten Datentyps äquivalent zur aufzurufenden Callback-Methode) zu funktionieren. |
Ist es denn wirklich undefiniert? Im grunde übergebe ich doch nur einen Pointer (auf etwas beliebiges).. was ich dann in der doSomething funktion damit mache ist mir ja überlassen.
Worauf ich hinauswollte war, ob es so geht wie oben geschrieben, oder ob es eher so aussehen muß:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| void doSomething(Parameters *p) { ((SimpleParameters*)p)->print(); }
[......]
SimpleEvent ev = doSomething; SimpleParameters param; param.a = 23; param.b = 42; ev((Parameters*)¶m); |
Das es so funktioniert weiß ich, ist ja auch alles völlig korrekt (wenn auch fehleranfällig wenn jemand falsch castet).
Nur bei dem anderen beispiel aus dem ersten post bin ich mir halt unsicher, weil ich nochnie einen callback umgecastet habe und daher nicht weiß ob das garantiert funktioniert so wie ich es auch vom zweiten beispiel erwarten würde.
Aya~
_________________ Aya
I aim for my endless dreams and I know they will come true!
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Do 28.05.09 10:30
Wie gesagt: Funktionieren mit Cast tut alles; sogar einen long double in einen Int zu speichern; nur ob das das gewünschte Ergebnis liefert, ist eine andere Frage
Und was ich mit "undefiniertes Verhalten" meinte ist genau die Geschichte mit dem Casten: Wenn man mehr nutzen möchte, wie einem garantiert wird, wird es schief gehen. Wenn man also nicht seine Randbedingungen überprüft, die einem ein gewisses Verhalten garantieren (ja, da wurde wirklich der gewünschte Typ übergeben, den man brauch), dann passiert alles mögliche: Im Zweifelsfalle die Formatierung der Festplatte mit vollständigem BIOS-Erase, weil der Methoden-Pointer, der normalerweise die Anzahl der Schafe zurückgibt auf die System.Kill-Methode zeigt.
Aya hat folgendes geschrieben : | Worauf ich hinauswollte war, ob es so geht wie oben geschrieben, oder ob es eher so aussehen muß:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| void doSomething(Parameters *p) { ((SimpleParameters*)p)->print(); }
[......]
SimpleEvent ev = doSomething; SimpleParameters param; param.a = 23; param.b = 42; ev((Parameters*)¶m); |
Das es so funktioniert weiß ich, ist ja auch alles völlig korrekt (wenn auch fehleranfällig wenn jemand falsch castet).
Nur bei dem anderen beispiel aus dem ersten post bin ich mir halt unsicher, weil ich nochnie einen callback umgecastet habe und daher nicht weiß ob das garantiert funktioniert so wie ich es auch vom zweiten beispiel erwarten würde.
Aya~ |
Funktionieren würde beides, aber korrekt (rein vom Typenhandling) ist allein die zweite Lösung, auch wenn im Callback die Typ-Prüfung (assert) vor dem Cast fehlt. Unsicheren Code sollte man vermeiden; selbst wenn es dadurch bequemer wird. Inwiefern der explizite Typecast bzgl. SimpleParameters --> Parameters im zweiten Beispiel nötig ist, weiß ich aber grad nicht.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
tommie-lie
      
Beiträge: 4373
Ubuntu 7.10 "Gutsy Gibbon"
|
Verfasst: Do 28.05.09 10:59
Um Bennys Worte zum undefined behavior mal etwas weiter zu verdeutlichen: IIRC garantiert der standard nicht, daß reinterpret_cast<long int>(static_cast<miep *>(someVar)) == reinterpret_cast<long int>(static_cast<moep *>(someVar)) wenn struct moep : miep. Ich kenne zwar keine Implementierung für x86, bei der das nicht so ist, aber vorstellbar wäre es, daß der static_cast<> tatsächlich einen anderen Pointer zurückgibt.
Um das ganze etwas typsicherer zu gestalten: wie wär's mit "the C++ way of function pointers", functors?
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
| class Parameters { public: int a; };
class SimpleParameters : public Parameters { public: int b; void print() const { std::cout << a << " .. " << b << std::endl; } };
template<typename ParamT> struct SimpleEvent { typedef void (*FunctionPtr_t)(ParamT const &); FunctionPtr_t const functionPtr; ParamT const ¶m;
SimpleEvent(FunctionPtr_t const &functionPtr, ParamT const ¶m) : functionPtr(functionPtr), param(param) {}
void operator()() { this->functionPtr(param); } };
void doSomething(SimpleParameters const &p) { p.print(); }
int main() { SimpleParameters param; param.a = 23; param.b = 42;
SimpleEvent<SimpleParameters> ev(&doSomething, param); ev(); } |
Das ist definiertes Verhalten, typsicher und in dem Fall sogar const-correct und kommt ganz ohne böse Typecasts aus. Functors sind generell viel toller als Funktionspointer, zum Beispiel weil sie late binding unterstützen (bestimmte Parameter zur Erzeugung des Functors festlegen, andere dem operator() übergeben) oder man mal eben die Reihenfolge der Parameter umändern kann.
_________________ Your computer is designed to become slower and more unreliable over time, so you have to upgrade. But if you'd like some false hope, I can tell you how to defragment your disk. - Dilbert
|
|
Aya 
      
Beiträge: 1964
Erhaltene Danke: 15
MacOSX 10.6.7
Xcode / C++
|
Verfasst: Fr 29.05.09 11:23
Hi,
so wie Tommie-lie das vorschlägt hatte ich es vorher.. der grund warum ich damit nicht so glücklich bin ist folgender:
Mein UI wird in einem XML File definiert, also welche button wo ist etc. Bisher hat jedes UI Element eine ID mit der ich es später im programm identifizieren kann, um z.B. dann auf ein onClick vom "Button23" zu reagieren.
Das ist zwar an und für sich okay so, aber ich möchte gern die möglichkeit haben später im Skin zwei buttons zu machen die die selbe funktion aufrufen. Und das geht dann nur indem ich den code im Programm anpasse, so das er auf mehr als einen Button reagiert.
Deswegen war mein Plan das ich im XML Skin definiere <button onClick="doSomething" ... /> und dann in meinem code einfach nur die doSomething-funktion registriere, á la:
C#-Quelltext 1:
| UI::registerEvent("doSomething", myDoSomethingFunction); |
Das würde mit meiner ersten variante funktionieren, mit der von tommie-lie nichtmehr.. ausser ich würde verschiedene registerEvent funktionen für jeden Event-Typ machen (also je nachdem was für EventParameter da übergeben werden)..
Oder hat jemand eine andere tolle idee für mein problem hier?
Aya~
_________________ Aya
I aim for my endless dreams and I know they will come true!
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Fr 29.05.09 11:59
hmmm. Template die RegisterEvent-Funktion 
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Aya 
      
Beiträge: 1964
Erhaltene Danke: 15
MacOSX 10.6.7
Xcode / C++
|
Verfasst: Sa 30.05.09 16:06
Hi,
mhh... die RegisterEvent-funktion zu templaten wäre ne möglichkeit, aber würde es auch alles sehr verkomplizieren  Aber führt wohl kein weg dran vorbei..
Ein anderes problem hab ich leider noch:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| class TestBase {};
class TestDeriv : public TestBase { public: void doSomething(int const &p) { cio << p << cinl; } };
typedef void (TestBase::*classEvent)(int const &);
[....]
classEvent ev = &TestDeriv::doSomething; classEvent ev = (classEvent)&TestDeriv::doSomething; |
Das problem ist, ich muß bei der Zuweisung des Evnts einen Cast machen... das finde ich sehr unschön
Gibt es da nen weg dran vorbei?
| C++ Compiler hat folgendes geschrieben: | | error: cannot convert 'void (TestDeriv::*)(const int&)' to 'void (TestBase::*)(const int&)' in initialization |
Aya~
_________________ Aya
I aim for my endless dreams and I know they will come true!
|
|
Th69
      

Beiträge: 4807
Erhaltene Danke: 1061
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: So 31.05.09 09:44
Das ist der Nachteil an den Klassenmethodenzeigern in C++: diese gelten nur exakt für die definierte Klasse, nicht für abgeleitete Klassen. Du wirst also auf den Cast NICHT verzichten können.
Evtl. wäre dann die libsigC++ etwas für dich: libsigc.sourceforge.net/
|
|
|