2010-12-30 4 views
1

Angenommen, wir haben den folgenden Code:Sind diese Zeigerkonvertierungen mit static_cast ok?

#include <iostream> 

struct A 
{ 
    virtual void f() { 
     std::cout << "A::f()" << std::endl; 
    } 
}; 

struct B: A 
{ 
    void f() { 
     std::cout << "B::f()" << std::endl; 
    } 
}; 

void to_A(void* voidp) { 
    A* aptr = static_cast<A*>(voidp); 
    aptr->f(); 
} 

void to_B(void* voidp) { 
    B* bptr2 = static_cast<B*>(voidp); 
    bptr2->f(); 
} 

int main() { 
    B* bptr = new B; 
    void* voidp = bptr; 
    to_A(voidp); // prints B::f() 
    to_B(voidp); // prints B::f() 
} 

diesen Code auf immer Arbeit wie in den Code-Kommentaren garantiert wird, oder es wird UB? AFAIK sollte es in Ordnung sein, aber ich möchte beruhigt sein.

EDIT
Ok, so scheint es, ein Konsens ist, dass dieser Code UB ist, wie man es nur auf den genauen Typ umwandeln kann. Also, was ist, wenn die main() Änderungen:

int main() { 
    B* bptr = new B; 
    to_A(static_cast<A*>(bptr)); // still prints B::f() 
    to_B(bptr); // still prints B::f() 
} 

ist es immer noch UB?

+0

mögliches Duplikat von [Wann sollten static_cast, dynamic_cast und reinterpret_cast verwendet werden?] (Http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-and-reinterpret-cast-be -used) –

+0

Der Code in EDIT kompiliert nicht ... Undefinierte Variable voidp. – Tomek

+0

@Tomek-Code behoben. – Simone

Antwort

3

Ihr erstes Codebeispiel ruft undefiniertes Verhalten auf.

können Sie einen static_cast verwenden, um eine Standardkonvertierung von Zeiger auf Reverse Typ Objekt zu void auf Zeiger, aber das Ergebnis ist nur gewährleistet, wenn der Wert des Zeigers auf void wieder auf den ursprünglichen Objekttyp umgewandelt wird, ist das Ergebnis der Standardkonvertierung eines Zeigers zum ursprünglichen Typ in Zeiger auf void.

Ihr zweites Codebeispiel ist OK, da Sie Konvertierungen nur von Zeiger zu Typ zu Zeiger zu Lücke umkehren, indem Sie auf den ursprünglichen Typ, von dem die Konvertierung durchgeführt wurde, zurückübertragen. Dies ist in 5.2.9 [expr.static.cast] des Standards (C++ 03) garantiert.

... Ein Wert des Typs Zeiger auf ein Objekt, das in "Zeiger auf cv void" konvertiert wurde, und zurück zum ursprünglichen Zeigertyp hat seinen ursprünglichen Wert.

+0

Also ist Patrick's Antwort falsch? – Simone

+1

@Simone: Ich bin mir nicht ganz sicher. Er sagt: "In diesem speziellen Fall funktioniert es", aber das kann nur die Ergebnisse interpretieren, die Sie sehen, es gibt keine Garantie. Er sagt dann "es gibt andere Fälle, in denen es nicht funktioniert", was auch richtig ist. Er führt eine Analyse durch, die auf einigen Implementierungsannahmen beruht, warum es funktionieren könnte. Ich ziehe es vor, explizit zu sein: Es ist undefiniertes Verhalten. –

+0

Ich denke, diese Antwort ist richtig und sollte daher akzeptiert werden. :-) – Nawaz

-2

Ich denke, Sie verwenden Casting nicht richtig. Ich würde empfehlen, die man von hier zuerst zwei Antworten zu lesen:

When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used?

Lassen Sie uns zunächst wissen, wann die verwendet werden gegossen sollte!


EDIT:

Was Ihre Frage,

dieser Code ist garantiert immer wie in den Code Kommentare zu arbeiten oder es ist UB? AFAIK sollte es in Ordnung sein, aber ich würde gerne beruhigt sein.

Wenn Sie Zeiger von jeder Art zu Ihrer to_A() Funktion zu übergeben wollen, dann denke ich static_cast nicht derjenige ist, sollten Sie nutzen. Sie sollten reinterpret_cast verwenden. Aber wenn Sie nur Zeiger auf B (oder eine abgeleitete Klasse von A) zu to_A() Funktion übergeben möchten, dann würde es immer arbeiten. Wenn Sie mit "Arbeit" arbeiten, haben Sie gemeint, dass es einen Nicht-Null-Wert zurückgibt.

Ähnliches Argument für to_B() Funktion. Ich denke, A* zu to_B()übergeben könnte fehlschlagen. Das heißt, es könnte einfach NULL zurückgeben, oder wenn nicht null, A::f() wird von to_B() aufgerufen werden.

Sehen Sie diese interessante Ausgabe: http://www.ideone.com/2FZzw (druckt A :: f(), nicht B :: f() !!!)

+0

Warum sollte es B :: f() drucken, wenn Sie eine A-Struktur instanziieren? http://www.ideone.com/nw1WI – Simone

+0

@ Simone: das ist, was verwirrend ist. Was meinst du mit "Arbeit"? Es hängt von Ihrer Absicht ab? Kannst du es in einen anderen Typ umwandeln? Ist das mit "Arbeit" gemeint? – Nawaz

+0

@Nawaz Ich meine, "B :: f()" in beiden Funktionsaufrufen zu drucken, wie in den Kommentaren beschrieben. – Simone

3

In diesem speziellen Fall es funktioniert, aber es gibt auch andere Fälle, in denen es nicht funktioniert.

Das Problem ist die Stelle, wo Sie den B-Zeiger auf Void-Zeiger auf A-Zeiger werfen.In diesem Fall die Zeiger werden alle den gleichen Wert haben, aber in den folgenden Bedingungen ist dies nicht mehr wahr:

  • die Basisklasse keine virtuellen Methoden hat (also keine vptr), aber die Unterklasse hat virtuelle Methoden (ich traf einmal einen solchen Fehler in der Software meiner Firma)
  • die Unterklasse von Mehrfachvererbung verwendet

der einzig sichere Weg, es zu werfen genau wieder auf die gleiche Art wie, wo Sie gekommen sind. Wenn Sie also einen B-Zeiger auf void-pointer setzen, werfen Sie ihn zurück auf einen B-Zeiger, nicht auf einen Zeiger auf eine andere Klasse, auch wenn sie zur selben Vererbungsstruktur gehören.

+0

Die Umwandlung von void * nach X * funktioniert nur dann, wenn der Zeiger ursprünglich ein X * war, mit dem er beginnen soll. Bei allen anderen Typen gibt es keine Garantie (dh das OP hat Glück, dass seine Klassen einfach sind). –

+0

meinst du, dass es zufällig funktioniert oder dass es * garantiert * funktioniert? – Simone

+0

@Simone: Es funktioniert zufällig. Es gibt keine Sprachgarantie dafür, welches Verhalten ein 'B *' in 'void *' konvertiert, dann 'static_cast' in' A * '. –

1

Dies ist eigentlich eine ziemlich allgemeine Sache zu versuchen, vor allem in Funktionen, die einen C-Callback erfordern, so muss man vorsichtig sein. Ein typischer C-API-Rückruf wie folgt aussieht:

void pfnCallback(void *); 

In Ihrer C++ Code, den Sie sich entscheiden, eine Basisklasse zu verwenden, diesen besonderen Rückruf immer handhaben und wir nennen es

struct BaseCallback 
{ 
virtual ~BaseCallback(); 
virtual call(); 
}; 

Wir auch eine einzelne Funktion schreiben, die wir immer für diese C-API verwenden:

void OurCallback(void * var) 
{ 
    BaseCallback * ourBase = static_cast< BaseCallback * >)(var); 
    ourBase->call(); 
} 

Wie wir immer von void * zu BaseCallback werden werden Gießen * wir müssen vorsichtig sein, wenn wir zuerst die Parameter in die andere Richtung liefern, die wir sind Wir werden den BaseCallback * immer auf void * umstellen.