Penso che ci sono diverse cose qui:
- Differenza tra variabile automatica e allocata dinamicamente variabile
- durata degli oggetti
- RAII
- C# parallelo
Automatico vs Dinamico
Una variabile automatica è una variabile di cui il sistema gestirà la durata. Diamo fosso variabili globali in questo momento, è complicato, e concentrarsi sul caso solita:
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass sc; // 3
sc.foo(); // 4
return 0; // 5
} // 6
Qui sc
è una variabile automatica. È garantito che sia completamente inizializzato (ovvero che il costruttore abbia la garanzia dell'esecuzione) dopo che l'esecuzione della riga (3) è stata completata correttamente. Il suo distruttore verrà automaticamente richiamato sulla linea (6).
Generalmente parliamo dell'ambito di una variabile: dal punto di dichiarazione alla parentesi di chiusura corrispondente; e la lingua garantisce la distruzione quando l'oscilloscopio verrà chiuso, sia esso con uno return
o un'eccezione.
Naturalmente non vi è alcuna garanzia nel caso in cui si invochi il temuto "comportamento non definito" che generalmente si traduce in un arresto anomalo.
D'altra parte, C++ ha anche variabili dinamiche, cioè variabili che vengono allocate utilizzando new
.
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass* sc = 0; // 3
sc = new SomeClass(); // 4
sc->foo(); // 5
return 0; // 6
} // 7 (!! leak)
Qui sc
è ancora una variabile automatica, tuttavia il tipo differisce: è ora un puntatore a una variabile di tipo SomeClass
.
In linea (3) sc
viene assegnato un valore di puntatore nullo (nullptr
in C++ 0x) perché non punta a nessuna istanza di SomeClass
. Si noti che la lingua non garantisce alcuna inizializzazione autonomamente, quindi è necessario assegnare in modo esplicito qualcosa altrimenti si avrà un valore spazzatura.
Sulla linea (4) costruiamo una variabile dinamica (utilizzando l'operatore new
) e assegniamo il suo indirizzo a sc
. Si noti che la variabile dinamica stessa è senza nome, il sistema ci fornisce solo un puntatore (indirizzo).
In linea (7) il sistema distrugge automaticamente sc
, tuttavia non distrugge la variabile dinamica a cui punta, e quindi ora abbiamo una variabile dinamica il cui indirizzo non è memorizzato da nessuna parte. A meno che non usiamo un garbage collector (che non è il caso in C++ standard), abbiamo quindi perso memoria perché la memoria della variabile non sarà recuperata prima che il processo finisca ... e anche allora il distruttore non verrà eseguito (peccato se avesse effetti collaterali).
durata degli oggetti
Herb Sutter ha molto interessanti articoli su questo soggetto. Ecco the first.
In sintesi:
- Un oggetto vive appena il costruttore viene eseguito fino al completamento. Significa che se il costruttore lancia, l'oggetto non è mai vissuto (consideralo un incidente di gravidanza).
- Un oggetto è morto non appena viene richiamato il suo distruttore, se il distruttore getta (questo è EVIL) non può essere tentato di nuovo perché non è possibile richiamare alcun metodo su un oggetto morto, è un comportamento non definito.
Se torniamo al primo esempio:
int main(int argc, char* argv[]) // 1
{ // 2
SomeClass sc; // 3
sc.foo(); // 4
return 0; // 5
} // 6
sc
è vivo dalla linea (4) per la linea (5) compreso. In linea (3) è in costruzione (che può fallire per un numero qualsiasi di ragioni) e in linea (6) viene distrutto.
RAII
RAII significa Risorse acquisizione è di inizializzazione. È un idioma per gestire le risorse, e in particolare per essere sicuro che le risorse verranno alla fine rilasciate una volta acquisite.
In C++, dal momento che non abbiamo garbage collection, questo idioma è applicata principalmente alla gestione della memoria, ma è utile anche per qualsiasi altro tipo di risorse: serrature in ambienti multithread, file serrature, prese/connessioni in rete, ecc ...
Se utilizzato per la gestione della memoria, viene utilizzato per accoppiare la durata della variabile dinamica alla durata di un determinato insieme di variabili automatiche, assicurando che la variabile dinamica non sopravviva (e vada persa).
Nella sua forma più semplice, è accoppiato ad una singola variabile automatica:
int main(int argc, char* argv[])
{
std::unique_ptr<SomeClass> sc = new SomeClass();
sc->foo();
return 0;
}
è molto simile al primo esempio, tranne che allocare dinamicamente un'istanza di SomeClass
. L'indirizzo di questa istanza viene quindi passato all'oggetto sc
, di tipo std::unique_ptr<SomeClass>
(è una funzione C++ 0x, se non disponibile è disponibile boost::scoped_ptr
). unique_ptr
garantisce che l'oggetto puntato verrà distrutto quando sc
viene distrutto.
In una forma più complessa, potrebbe essere accoppiato a diverse variabili automatiche utilizzando (ad esempio) std::shared_ptr
, che come suggerisce il nome consente di condividere un oggetto e garantisce che l'oggetto verrà distrutto quando l'ultimo condivisore viene distrutto. Fai attenzione che questo non è equivalente all'utilizzo di un garbage collector e ci possono essere problemi con cicli di riferimenti, non approfondirò qui, quindi ricorda che lo std::shared_ptr
non è una panacea.
Perché è molto complicato da gestire perfettamente la durata di una variabile dinamica senza RAII di fronte alle eccezioni e il codice multi-threaded, la raccomandazione è:
- uso variabili automatiche, per quanto possibile
- per dinamica variabili, non invocano
delete
sul proprio e fa sempre uso delle strutture RAII
considero personalmente qualsiasi occorrenza di delete
ad essere fortemente sospetto, e ho sempre puntuale Chiediamo la sua rimozione nelle recensioni del codice: è un odore di codice.
C# parallelo
In C# si utilizza principalmente variabili dinamiche *
. Questo è il motivo per cui:
- Se hai appena dichiara una variabile, senza assegnazione, il suo valore è nullo: in sostanza si stanno manipolando solo puntatori e si ha quindi un puntatore nullo (inizializzazione è garantito, grazie al cielo)
- Usa
new
per creare valori, invoca il costruttore del tuo oggetto e ti fornisce l'indirizzo dell'oggetto; si noti come la sintassi è simile a C++ per le variabili dinamiche
Tuttavia, a differenza di C++, C# è spazzatura raccolti in modo da non dovete preoccuparvi di gestione della memoria.
Essere garbage collection significa anche che la vita degli oggetti è più difficile da capire: sono costruiti quando li chiedi ma sono distrutti a discrezione del sistema. Questo può essere un problema per implementare RAII, ad esempio se si desidera realmente rilasciare il blocco rapidamente e la lingua dispone di numerose funzionalità che consentono di semplificare l'interfaccia using
parola chiave + IDisposable
dalla memoria.
*
: è facile controllare, se dopo aver dichiarato una variabile il suo valore è null
, allora sarà una variabile dinamica. Credo che per il int
il valore sarà 0 che indica che non lo è, ma sono passati 3 anni da quando ho manipolato C# per un progetto di corso quindi ...
"Come vengo dal mondo C#" Allora? C++ non è C#, dimentica di sapere C# a tutti. Hai bisogno di un [buon libro per principianti] (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) e inizi da zero. – GManNickG
Grazie per l'elenco! Vero, C++ e C# sono due lingue molto diverse. Immagino che non voglio leggere su quali loop o classi sono e su come funzionano in C++, quindi ho evitato libri. Guardo la lista. –
@ Michael: non puoi evitare libri. :) C++ è la propria lingua. Sembra che tu pensi di capire già le classi C++, ma non sono la stessa cosa di C#. Certo, potresti già sapere quali sono le istruzioni di selezione o di iterazione, ma questo è solo un capitolo. – GManNickG