2010-03-09 15 views
12

Mi chiedo, per nessun altro scopo che pura curiosità (perché nessuno DOVREBBE mai scrivere un codice come questo!) Su come il comportamento di RAII si configura con l'uso di goto (bella idea non è) .Cosa succede quando uniamo RAII e GOTO?

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(); 
} 

Quando si esegue il seguente codice in Visual Studio 2005 ottengo il seguente output.

1,2,3 BOO! 

Tuttavia ho immaginato, indovinato, sperato che 'BOO!' non appare in realtà come Ghost non dovrebbe mai essere istanziato (IMHO, perché non conosco l'effettivo comportamento previsto di questo codice).

Che succede?


Ho appena realizzato che se un'istanza di un costruttore esplicito per Ghost il codice non viene compilato ...

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

Ah, il mistero ...

+1

Credo che il comportamento sia corretto. Altrimenti, come potresti riferire la variabile g dopo JUMP? – leiz

+2

http://xkcd.com/292/ –

risposta

23

I colloqui standard di circa questo esplicitamente - con un esempio; 6.7/3 "Dichiarazione della dichiarazione" (enfasi aggiunta da me):

Le variabili con durata di archiviazione automatica vengono inizializzate ogni volta che viene eseguita la dichiarazione di dichiarazione. Le variabili con durata di archiviazione automatica dichiarata nel blocco vengono eliminate all'uscita dal blocco.

È possibile trasferire in un blocco, ma non in un modo che ignora le dichiarazioni con l'inizializzazione. Un programma che salta da un punto in cui una variabile locale con durata dell'archiviazione automatica non è nell'ambito di un punto in cui è nell'ambito dell'ambito è mal formata a meno che la variabile non abbia il tipo POD e sia dichiarata senza un inizializzatore.

[Esempio:

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 
} 

-end esempio]

Quindi mi sembra che il comportamento di MSVC non è conforme agli standard - Ghost non è un tipo di POD, in modo che il compilatore dovrebbe emettere un errore quando l'istruzione goto è codificata per saltare oltre.

Un paio di altri compilatori che ho provato (GCC e Digital Mars) emettono errori. Comeau emette un avvertimento (ma in tutta onestà, il mio script di build per Comeau lo ha configurato per l'alta compatibilità MSVC, quindi potrebbe seguire intenzionalmente il lead di Microsoft).

+1

Grazie per aver trovato il punto in cui questo è definito nello standard! Tuttavia mi chiedo se mal formato voglia o non debba compilare ... –

+0

"Ill-formed" significa che il programma non è "ben formato". Ai compilatori è richiesto di accettare e "eseguire correttamente" programmi che sono ben formati. Cioè, se è "mal formata", è in errore. – greyfade

+0

@Robert: ho aggiunto alcune parole sul comportamento di MSVC qui, come avrei dovuto fare inizialmente. –

0

Goto non è radioattivo. Uscire da goto è poco diverso dal lasciare un'eccezione. Inserire per goto dovrebbe essere dettato dalla convenienza, non dai limiti della lingua. Non sapere se il fantasma è costruito o meno è una buona ragione per non farlo.

Salta prima del costruttore. Se vuoi saltare dentro dopo che alcuni oggetti sono già stati costruiti, racchiudili in un nuovo scope o in altro modo risolvilo da solo.

0

In questo scenario, ho trovato il seguente approccio utile.

void foo() 
{ 
    { 
     Two t; 
     printf("1,"); 
     goto JUMP; 
    } 

    { 
     Ghost g; 
     // operations that use g. 
    } 

// g is out of scope, so following JUMP is allowed. 
JUMP: 
    printf("3"); 
} 

Confinando l'ambito della variabile g nella funzione foo(), il tag goto salterà in modo legale. Ora, non saltiamo da un punto in cui g non è inizializzato in un punto in cui è previsto che g sia inizializzato.

Problemi correlati