void* in ursprüngliche abgeleitete klasse casten
-
Hi,
ich habe folgendes Problem:
class Base { public: virtual void do_it(); }; class Child : public Base { public: virtual void do_it(); }; void c_style_function(void *arg) { Base *self = (Base*)arg; self->do_it(); }Das verhält sich bei einem Aufruf der Art c_style_function( child ) nicht wie erwünscht, da immer Base::do_it() aufgerufen wird. Wie kann ich es am schönsten hinkriegen, dass immer die zur Klasse entsprechende virtuelle Funktion aufgerufen wird?
-
Warum übergibtst du nicht gleich einen Basisklassenzeiger?
void*hat in der Polymorphie grundsätzlich nichts zu suchen.
-
wenn man es 'richtig' ,acht, dann geht das auch.
class Base { public: virtual void do_it(){std::cout<<"BasE";}; }; class Child : public Base { public: virtual void do_it(){std::cout<<"Child";}; }; void c_style_function(void *arg) { Base *self = (Base*)arg; self->do_it(); } int main() { Child c; c_style_function(&c); Base b; c_style_function(&b); }
Fehler is wo anders.
-
Hi,
Nexus schrieb:
Warum übergibtst du nicht gleich einen Basisklassenzeiger?
void*hat in der Polymorphie grundsätzlich nichts zu suchen.die Funktion ist ein Callback einer C-Bibliothek, die ich benutze.
-
Der Fehler muss irgendwo anders liegen, das geht einwandfrei. Das einzige was "falsch" ist, ist dein C-Cast. In C++ benutzt man für sowas ein
static_cast:void c_style_function(void *arg) { Base *self = static_cast<Base*>(arg); self->do_it(); }Grüssli
-
Dravere schrieb:
Der Fehler muss irgendwo anders liegen, das geht einwandfrei. Das einzige was "falsch" ist, ist dein C-Cast. In C++ benutzt man für sowas ein
reinterpret_cast:void c_style_function(void *arg) { Base *self = reinterpret_cast<Base*>(arg); self->do_it(); }Grüssli
Warum? Welchen Zweck soll reinterpret_cast hier erfüllen, dem static_cast nicht ebenso gut dienen kann?
-
camper schrieb:
Warum? Welchen Zweck soll reinterpret_cast hier erfüllen, dem static_cast nicht ebenso gut dienen kann?
Ehm ... die Bakterien, welche mich überfallen haben, vernebeln mir wohl noch etwas das Hirn. Klar, static_cast ... Ich korrigiere das in meinem obigem Post, danke für den Hinweis.
Jedenfalls sollte man den C-Cast vermeiden
Grüssli
-
Zu beachten ist, dass man einen void-Zeiger nur in den Typ zürückcasten kann, der er ursprünglich war. Man kann also der Funktion keinen Zeiger auf Derived geben, da intern auf Base gecastet wird.
-
Don06 schrieb:
Zu beachten ist, dass man einen void-Zeiger nur in den Typ zürückcasten kann, der er ursprünglich war. Man kann also der Funktion keinen Zeiger auf Derived geben, da intern auf Base gecastet wird.
Was? Das gibt doch garkeinen sinn was du da schreibst.
-
Ok, ich habe rausgefunden, was das problem war: Es ist isomorph zu folgendem Beispiel:
#include <iostream> using namespace std; void c_style_callback(void *p); class Base { public: Base() { c_style_callback(this); } virtual void do_it() { cout << "Base::do_it()" << endl; } }; class Child : public Base { public: virtual void do_it() { cout << "Child::do_it()" << endl; } }; void c_style_callback(void *p) { Base *self = static_cast<Base*>(p); self->do_it(); } int main() { Base b; Child c; c_style_callback(&b); c_style_callback(&c); }Und da der Konstruktor nicht virtuell ist, ist this vom Typ (Base)*
-
PaulM schrieb:
Und da der Konstruktor nicht virtuell ist, ist this vom Typ (Base)*
Konstruktoren sind nie virtuell, nur Destruktoren. Das kann es also nicht sein.
-
Edit 3:
Hi,
danke, das hat mir geholfen. Den Zeiger in einer außerhalb des Konstruktors aufgerufenen virtuellen Funktion zu übergeben scheint der vernünftigste Weg zu sein.
-
PaulM schrieb:
Und da der Konstruktor nicht virtuell ist, ist this vom Typ (Base)*
Das ist aber nicht das tatsächliche Problem! Das Problem hier ist, dass Child noch gar nicht konstruiert wurde. Ich weiss jetzt gar nicht mehr, ob es undefiniertes Verhalten war oder nicht, wenn man virtuelle Funktionen im Konstruktor aufruft. Jedenfalls sollte man ganz klar das Aufrufen von virtuellen Funktionen in Konstruktoren vermeiden, da nicht garantiert ist, dass das ganze Objekt bereits gebaut wurde.
Das Problem ist somit, dass du eine virtuelle Funktion in einem Konstruktor aufrufst. Unter diesem Begriff kannst du auch suchen gehen, gab auch schon mal Threads in diesem Forum dazu.
Grüssli
-
Die virtuelle Funktion ist nicht das Problem. Wohl aber
c_style_callback(&c);Das Argument wird in der Funktion in ein Base* konvertiert, obwohl es eigentlich die Adresse eines Child-Objektes ist. Da Base und Child nicht die gleiche Adresse haben müssen, kann das undefiniert sein. Der void*-Parameter ist ohnehin unsinnig: da wir voraussetzen, dass der Zeiger auf ein Base-Objekt zeigen soll, können wir das gleich durch den Zeigertypen klarstellen.
void c_style_callback(Base *p);Dann ist auch der Aufruf mit &c kein Problem.
-
@camper,
Jetzt liegst du aber mal teilweise daneben! Ha! (Ich hoffe es stimmt wirklich und die Genugtuung hält an :D)Er hat gesagt, dass
void c_style_callback(void *p);ein vordefinierte Callbackfunktion aus einer C-Bibliothek ist. Also kann man da nichts ändern! Finger weg :pZudem ist mir neu, dass in der NICHT-Mehrfachvererbung der Basiszeiger nicht mit dem Childzeiger übereinstimmen kann. Bei Mehrfachvererbung bin ich sofort damit einverstanden, aber bisher habe ich gelehrt, dass in der einfachen Vererbung der Zeiger genau übereinstimmt.
Das Problem liegt allerdings immer noch am Aufruf von
c_style_callback(this);im Base-Konstruktor! Vor allem dann, wenn das Child-Objekt in dermainFunktion gebaut wird. Da wird dann die falsche Funktion aufgerufen.Grüssli
-
camper schrieb:
[...]Da Base und Child nicht die gleiche Adresse haben müssen[...]
Wenn dem so wäre, müsste man wohl auf Polymorphie verzichten.
-
PaulM schrieb:
Hi,
Nexus schrieb:
Warum übergibtst du nicht gleich einen Basisklassenzeiger?
void*hat in der Polymorphie grundsätzlich nichts zu suchen.die Funktion ist ein Callback einer C-Bibliothek, die ich benutze.
Irgendwie will mir die Aussage so gar nicht einleuchten. Du hast eine Callbackfunktion aus einer C-Bibliothek, und über die bekommst Du eine C++ Objekt übergeben, das zu allem Überfluss auch noch polymorph ist? Bist Du sicher, dass Du alles richtig machst?
-
Dravere schrieb:
Zudem ist mir neu, dass in der NICHT-Mehrfachvererbung der Basiszeiger nicht mit dem Childzeiger übereinstimmen kann. Bei Mehrfachvererbung bin ich sofort damit einverstanden, aber bisher habe ich gelehrt, dass in der einfachen Vererbung der Zeiger genau übereinstimmt.
Eine sehr subtil falsche Annahme.
Grundsätzlich kann sich auch bei einem Up-Cast der Zeiger ändern!
Angenommen du hast 2 Klassen A und B, B erbt von A, A hat keine virtuellen Funktionen (und somit keine vtable), B hat welche (und somit eine vtable).
In dieser Konstellation legen manche (vielleicht sogar viele?) Compiler im Speicherlayout den vtableptr von B vor A, während die Member von B nach A kommen. Dann beginnt B bei (&a - sizeof(ptr)) Das kann z.B. zur einfacheren Offsetberechnung sinnvoll sein. Wenn du mehr darüber wissen willst, könnte ich dir "Inside The C++ Object Model" von Stanley B. Lippman empfehlen.In jedem Fall solltest du darauf gefasst sein, dass der Zeiger sich auch in einem Up-Cast "ändern" darf und kann, auch wenn keine Mehrfach/Virtuelle Vererbung im Spiel ist.
Der obige Code würde mit c_style_callback( static_cast<B*>( &c)); korrekt sein.
Tachyon schrieb:
Wenn dem so wäre, müsste man wohl auf Polymorphie verzichten.
Den musst du mir jetzt aber mal erklären.
-
Hi,
Tachyon schrieb:
PaulM schrieb:
Hi,
Nexus schrieb:
Warum übergibtst du nicht gleich einen Basisklassenzeiger?
void*hat in der Polymorphie grundsätzlich nichts zu suchen.die Funktion ist ein Callback einer C-Bibliothek, die ich benutze.
Irgendwie will mir die Aussage so gar nicht einleuchten. Du hast eine Callbackfunktion aus einer C-Bibliothek, und über die bekommst Du eine C++ Objekt übergeben, das zu allem Überfluss auch noch polymorph ist?
Ich habe das ganze für meine Frage etwas vereinfacht. Die Bibliothek, die ich verwende, heißt libjack. Ich registriere bei der Bibliothek eine Funktion, die dann regelmäßig aufgerufen wird. Und ich kann bei der Registrierung einen void* übergeben, der jedesmal an die callback-Funktion übergeben wird. D.h., ich kann den Inhalt der callback-Funktion verändern, jedoch nicht die Deklaration.
Nun habe ich versucht, einen OO-Wrapper zu schreiben. Base kapselt die Initialisierung von libjack, und Child implementiert lediglich eine virtuelle Funktion, die die eigentliche Arbeit macht. Diese virtuelle Funktion wird aus der callback-Funktion aufgerufen.Bist Du sicher, dass Du alles richtig machst?
selten

-
Dravere schrieb:
@camper,
Jetzt liegst du aber mal teilweise daneben! Ha! (Ich hoffe es stimmt wirklich und die Genugtuung hält an :D)Ich mache durchaus nicht weniger Fehler als andere. Ich versuche aber, die Fehler anderer nicht zu wiederholen.
Dravere schrieb:
Er hat gesagt, dass
void c_style_callback(void *p);ein vordefinierte Callbackfunktion aus einer C-Bibliothek ist. Also kann man da nichts ändern! Finger weg :pDas ändert nichts. Wenn man die Funktion nicht ändern kann, ruft man eben eine andere auf, d.h. einen Wrapper mit hinreichend restriktiven - also typsicherem Interface. void* ist grundsätzlich nur in zwei Situation sinnvoll. Einmal wenn man - aus welchen Gründen auch immer - einen Zeiger in einem generischen Typen speichern muss, sofern der Wert in den exakt gleichen Zeigertyp zurückkonvertiert werden kann. Zweitens für bestimmte low-level-Operationen (man denke an memcpy&co). Beides trifft hier nicht zu, folglich hat die vorgestellte Funktion keine Daseinsberechtigung sowohl als framework-spezifische Funktion als auch direkt aufzurufendes Interface zu dienen.
Dravere schrieb:
Zudem ist mir neu, dass in der NICHT-Mehrfachvererbung der Basiszeiger nicht mit dem Childzeiger übereinstimmen kann. Bei Mehrfachvererbung bin ich sofort damit einverstanden, aber bisher habe ich gelehrt, dass in der einfachen Vererbung der Zeiger genau übereinstimmt.
Dann kannst du noch etwas dazulernen. Das Objektmodell des Standards macht keine Aussage darüber, wo in einem abgeleiten Objekt das Basisklassensubobjekt zu finden ist - offensichtlich können die Adressen bei virtueller oder bei Mehrfachvererbung im allgemeinen nicht übereinstimmen. Aber auch für die einfache Vererbung wird nichts festgelegt. Es kommt auch gar nicht darauf an, ob irgendein realer Compiler deiner Annahme widerspricht. Vielmehr solltest du dir die Frage stellen - warum du dir überhaupt bei solchen Highlevel-konstrukten Gedanken über die Niederungen des Objektlayouts gedanken machen solltest/müsstest. Diese Durchbrechung von Abstraktionsebenen ist oft der Ausgangspunkt böser wtfs. Zudem gibt es keine (einfache) Möglichkeit, Einfachvererbung beim Compilieren zu erzwingen. Niemand kann voraussagen, wie sich der Code im Laufe der Zeit entwickeln wird, und ob nicht evtl. später mal Mehrfach- oder virtuelle Vererbung ins Spiel kommt. Mal abgesehen davon, dass die Funktion so ohnehin mit irgendwelchen beliebigen Zeigern gefüttert werden kann.
Dravere schrieb:
Das Problem liegt allerdings immer noch am Aufruf von
c_style_callback(this);im Base-Konstruktor! Vor allem dann, wenn das Child-Objekt in dermainFunktion gebaut wird. Da wird dann die falsche Funktion aufgerufen.Und auch hier wiederspreche ich. Die aufgerufene Funktion ist die Version der Basisklasse, klar. Ob das allerdings ein Fehler ist, kannst du nur dadurch herausfinden, indem du es mit der Intention des Programmierers vergleichst. Der möchte, das immer die Version der abgeleiteten Klasse aufgerufen wird, und das ist nat. im Basisklassenkonstruktor unmöglich. Nicht aber das virtuell ist hier das Problem - sondern die Tatsache, das kein abgeleitetes Objekt existiert. Schließlich darf auch eine nicht-virtuelle nicht-statische Funktion der abgeleiteten Klasse nicht mit diesem noch zu konstruierenden Objekt aufgerufen werden.
-
camper schrieb:
Ich mache durchaus nicht weniger Fehler als andere. Ich versuche aber, die Fehler anderer nicht zu wiederholen.
Du machst vergleichsweise allerdings wenig Fehler. Um ehrlich zu sein, habe ich bisher in diesem Forum von dir noch keinen gesehen

Zudem habe ich wohl wegen meinem Vater, welcher vergleichsweise auch wenig Fehler macht, irgendwo einen Komplex, dass ich mich bei solchen Personen immer wieder extrem darüber freue, wenn sie Fehler machen.
camper schrieb:
Das ändert nichts. Wenn man die Funktion nicht ändern kann, ruft man eben eine andere auf, d.h. einen Wrapper mit hinreichend restriktiven - also typsicherem Interface. void* ist grundsätzlich nur in zwei Situation sinnvoll. Einmal wenn man - aus welchen Gründen auch immer - einen Zeiger in einem generischen Typen speichern muss, sofern der Wert in den exakt gleichen Zeigertyp zurückkonvertiert werden kann. Zweitens für bestimmte low-level-Operationen (man denke an memcpy&co). Beides trifft hier nicht zu, folglich hat die vorgestellte Funktion keine Daseinsberechtigung sowohl als framework-spezifische Funktion als auch direkt aufzurufendes Interface zu dienen.
Wenn du einen Wrapper oder was auch immer machst, irgendwo und irgendwann musst du die Funktion aufrufen. Das ändert nichts an der Tatsache, dass du die Finger von der Funktion lassen musst. Wahrscheinlich ist hier eine Callback Funktion definiert, welche erwartet wird, à la:
void (*Func)(void*) callback;Und nicht jeder wird nun auch gleich einer C++ Wrapper für jede C-Bibliothek erstellen, obwohl es natürlich eigentlich zu empfehlen wäre

Zur Sache mit den Zeigern, dass wusste ich wirklich nicht. Bisher wurde mir in jeglichem Buch erklärt, dass sich die Objekte einfach hintereinander auf den Speicher legen und somit die gleiche Adresse haben. Also veranschaulicht:
| Object der Klasse A | Objekt der Klasse B (erbt von A) | |---------------------|----------------------------------| | x Bytes ... | y Bytes ... | |---------------------|----------------------------------|Aber man lernt ja bekanntlich nie aus

Es kam bei mir aber auch noch nie soweit, dass ich das irgendwie benötigt hätte. Habe glaub ich noch nie von einer abgeleiteten Klasse auf void* und dann zurück auf eine Basis rumgereicht. Das wäre mir persönlich schon einfach nur vom Ansehen her, zu grässlich gewesen.Die Lösung von dem wäre dann aber ziemlich einfach:
c_style_function(static_cast<Base*>(&child));camper schrieb:
Dravere schrieb:
Das Problem liegt allerdings immer noch am Aufruf von c_style_callback(this); im Base-Konstruktor! Vor allem dann, wenn das Child-Objekt in der main Funktion gebaut wird. Da wird dann die falsche Funktion aufgerufen.
Und auch hier wiederspreche ich.
Das freut mich!

camper schrieb:
Die aufgerufene Funktion ist die Version der Basisklasse, klar. Ob das allerdings ein Fehler ist, kannst du nur dadurch herausfinden, indem du es mit der Intention des Programmierers vergleichst.
Und da liegt dein Fehler! Wir wissen was die Intention des Programmierers ist, darf ich vorstellen:
PaulM schrieb:
Das verhält sich bei einem Aufruf der Art c_style_function( child ) nicht wie erwünscht, da immer Base::do_it() aufgerufen wird. Wie kann ich es am schönsten hinkriegen, dass immer die zur Klasse entsprechende virtuelle Funktion aufgerufen wird?
Wie du dann im weiteren Satz auch sagst:
camper schrieb:
Der möchte, das immer die Version der abgeleiteten Klasse aufgerufen wird, und das ist nat. im Basisklassenkonstruktor unmöglich. Nicht aber das virtuell ist hier das Problem - sondern die Tatsache, das kein abgeleitetes Objekt existiert. Schließlich darf auch eine nicht-virtuelle nicht-statische Funktion der abgeleiteten Klasse nicht mit diesem noch zu konstruierenden Objekt aufgerufen werden.
Und ich habe gesagt:
Dravere schrieb:
PaulM schrieb:
Und da der Konstruktor nicht virtuell ist, ist this vom Typ (Base)*
Das ist aber nicht das tatsächliche Problem! Das Problem hier ist, dass Child noch gar nicht konstruiert wurde.
Es tut mir leid, aber es ist und bleibt der tatsächliche Fehler.
Das andere mit dem
c_style_function(&child);kommt ursprünglich nicht vom Threadersteller sondern von jemand anderem als Beispiel:
http://www.c-plusplus.net/forum/viewtopic-var-p-is-1597813.html#1597813Ich weiss es nun und du wusstest es schon lange, dass dies zwar falsch ist, hat aber mit dem ursprünglichen und tatsächlichen Fehler nichts zu tun.
(Was für ein Drama ich hier mache
)Grüssli