2010-03-09 6 views
12

Ich frage mich, für keinen anderen Zweck als pure Neugier (denn niemand sollte jemals Code so schreiben!) Über das Verhalten von RAII Meshes mit der Verwendung von goto (schöne Idee ist es nicht) .Was passiert, wenn wir RAII und GOTO kombinieren?

class Two 
{ 
public: 
    ~Two() 
    { 
     printf("2,"); 
    } 
}; 

class Ghost 
{ 
public: 
    ~Ghost() 
    { 
     printf(" BOO! "); 
    } 
}; 

void foo() 
{ 
    { 
     Two t; 
     printf("1,"); 
     goto JUMP; 
    } 
    Ghost g; 
JUMP: 
    printf("3"); 
} 

int main() 
{ 
     foo(); 
} 

Wenn Sie den folgenden Code in Visual Studio 2005 ich die folgende Ausgabe.

1,2,3 BOO! 

aber ich dachte, erraten, zu hoffen, dass 'BOO!' würde nicht tatsächlich als Ghost sollte nie instanziiert worden sein (IMHO, weil ich nicht das tatsächliche erwartete Verhalten dieses Codes kennen).

Was ist los?


Ich habe erkannt, dass, wenn ich einen expliziten Konstruktor für Ghost instanziiert der Code nicht kompiliert ...

class Ghost 
{ 
public: 
    Ghost() 
    { 
     printf(" HAHAHA! "); 
    } 
    ~Ghost() 
    { 
     printf(" BOO! "); 
    } 
}; 

Ah, das Geheimnis ...

+1

Ich glaube, das Verhalten ist korrekt. Wie sonst könnte man die Variable g nach JUMP beziehen? – leiz

+2

http://xkcd.com/292/ –

Antwort

23

Die Standard spricht über dies explizit - mit einem Beispiel; 6.7/3 "Declaration Statement" (Hervorhebung von mir hinzugefügt):

Variablen mit automatischer Speicherdauer werden jedes Mal initialisiert, wenn ihre Deklarationsanweisung ausgeführt wird. Variablen mit automatischer Speicherdauer, die im Block deklariert sind, werden beim Verlassen des Blocks zerstört.

Es ist möglich, in einen Block zu übertragen, aber nicht so, dass Deklarationen bei der Initialisierung umgangen werden. Ein Programm, das von einem Punkt springt, an dem eine lokale Variable mit automatischer Speicherdauer nicht im Gültigkeitsbereich liegt, bis zu einem Punkt, an dem es sich im Gültigkeitsbereich befindet, ist fehlerhaft, es sei denn, die Variable hat den POD-Typ und wird ohne Initialisierer deklariert.

[Beispiel:

void f() 
{ 
    //... 
    goto lx; //ill-formed: jump into scope of a 
    //... 

ly: 
    X a = 1; 
    //... 

lx: 
    goto ly; //OK, jump implies destructor 
       //call for a, followed by construction 
       //again immediately following label ly 
} 

-Ende Beispiel]

So scheint es mir, dass MSVC Verhalten nicht standardkonform ist - Ghost ist kein POD-Typ, so dass der Compiler sollte eine Ausgabe Fehler, wenn die goto-Anweisung codiert ist, um darüber zu springen.

Ein paar andere Compiler, die ich ausprobiert habe (GCC und Digital Mars), geben Fehler aus. Comeau gibt eine Warnung aus (aber fairerweise wurde mein Build-Skript für Comeau für hohe MSVC-Kompatibilität konfiguriert, so dass es absichtlich Microsofts Führung folgt).

+1

Vielen Dank für die Suche nach der Stelle, wo dies im Standard definiert ist! Allerdings frage ich mich, ob schlecht gebildet bedeutet, dass es kompilieren sollte oder nicht ... –

+0

"Ill-formed" bedeutet, dass das Programm nicht "wohlgeformt" ist. Compiler müssen nur gut formatierte Programme akzeptieren und "richtig ausführen". Das heißt, wenn es "schlecht geformt" ist, ist es ein Fehler. – greyfade

+0

@Robert: Ich habe hier ein paar Worte über das Verhalten von MSVC hinzugefügt, wie ich es ursprünglich hätte tun sollen. –

0

Goto ist nicht radioaktiv. Das Verlassen von goto ist etwas anderes als das Verlassen von Ausnahmen. Das Eingeben mit goto sollte von der Bequemlichkeit diktiert werden, nicht von den Grenzen der Sprache. Nicht zu wissen, ob der Geist konstruiert ist oder nicht, ist ein guter Grund, das nicht zu tun.

Vor dem Konstruktor einspringen. Wenn Sie nach dem Erstellen eines Objekts in das Objekt hineinspringen möchten, schließen Sie es in einen neuen Bereich ein oder lösen Sie die Lebensdauer des Objekts selbst auf.

0

In diesem Szenario habe ich folgenden Ansatz nützlich gefunden.

Wenn Sie den Gültigkeitsbereich der Variablen g in Ihrer Funktion foo() einschränken, wird der Sprung legal. Nun springen wir nicht von einem Ort, an dem g nicht initialisiert wird, zu einem Ort, an dem g initialisiert werden soll.