Da aggiungere al dibattito qui.
Esistono problemi noti con Garbage Collection e la loro comprensione aiuta a capire perché non esiste nessuno in C ++.
1. Prestazioni?
La prima lamentela riguarda spesso le prestazioni, ma la maggior parte delle persone non capisce davvero di cosa sta parlando. Come illustrato dal Martin Beckett
problema potrebbe non essere la prestazione in sé, ma la prevedibilità della prestazione.
Attualmente ci sono 2 famiglie di GC ampiamente utilizzate:
- Tipo Mark-And-Sweep
- Tipo di conteggio di riferimento
Il Mark And Sweep
è più veloce (meno impatto sulle prestazioni complessive), ma soffre di una sindrome di "congelare il mondo": vale a dire quando i calci GC in, tutto il resto è fermato fino a quando il GC ha fatto la sua pulizia. Se desideri costruire un server che risponda in pochi millisecondi ... alcune transazioni non saranno all'altezza delle tue aspettative :)
Il problema Reference Counting
è diverso: il conteggio dei riferimenti aggiunge un sovraccarico, specialmente negli ambienti multi-thread perché è necessario disporre di un conteggio atomico. Inoltre c'è il problema dei cicli di riferimento, quindi è necessario un algoritmo intelligente per rilevare quei cicli ed eliminarli (generalmente implementare anche un "congelare il mondo", anche se meno frequente). In generale, ad oggi, questo tipo (anche se normalmente più reattivo o meglio, si blocca meno spesso) è più lento di Mark And Sweep
.
Ho visto un documento degli implementatori di Eiffel che cercavano di implementare un Reference Counting
Garbage Collector che avrebbe avuto una performance globale simile a quella Mark And Sweep
senza l'aspetto "Freeze The World". Richiede un thread separato per il GC (tipico). L'algoritmo è stato un po 'spaventoso (alla fine) ma il documento ha fatto un buon lavoro introducendo i concetti uno alla volta e mostrando l'evoluzione dell'algoritmo dalla versione "semplice" a quella a tutti gli effetti. Lettura consigliata se solo potessi rimettere le mani sul file PDF ...
2. Acquisizione delle risorse è inizializzazione (RAII)
È un linguaggio comune in C++
quanto avvolgerai la proprietà delle risorse all'interno di un oggetto per assicurarti che vengano rilasciate correttamente. Viene utilizzato principalmente per la memoria poiché non abbiamo Garbage Collection, ma è utile anche per molte altre situazioni:
- blocchi (multi-thread, handle di file, ...)
- connessioni (a un database, un altro server, ...)
L'idea è di controllare correttamente la durata dell'oggetto:
- dovrebbe essere vivo finché ne hai bisogno
- dovrebbe essere ucciso quando hai finito con esso
Il problema di GC è che se aiuta con il primo e alla fine garantisce che in seguito ... questo "ultimo" potrebbe non essere sufficiente. Se rilasci un lucchetto, ti piacerebbe davvero che fosse rilasciato ora, in modo che non blocchi ulteriori chiamate!
Le lingue con GC hanno due soluzioni:
- non usare GC quando l'allocazione dello stack è sufficiente: è normalmente per problemi di prestazioni, ma nel nostro caso aiuta davvero poiché l'ambito definisce la durata
using
costruire ... ma è RAII esplicito (debole) mentre in C ++ RAII è implicito in modo che l'utente NON PUO 'fare involontariamente l'errore (omettendo la using
parola chiave)
3. Puntatori intelligenti
I puntatori intelligenti appaiono spesso come un proiettile d'argento per gestire la memoria C++
. Spesso ho sentito: dopotutto non abbiamo bisogno di GC, dato che abbiamo puntatori intelligenti.
Uno non potrebbe essere più sbagliato.
I puntatori intelligenti aiutano: auto_ptr
e unique_ptr
usano concetti RAII, estremamente utili. Sono così semplici che puoi scriverli da solo abbastanza facilmente.
Quando è necessario condividere la proprietà, tuttavia, diventa più difficile: è possibile condividere tra più thread e ci sono alcuni problemi sottili con la gestione del conteggio. Pertanto, si va naturalmente verso shared_ptr
.
È fantastico, questo è ciò per cui Boost dopo tutto, ma non è un proiettile d'argento. In effetti, il problema principale shared_ptr
è che emula un GC implementato da Reference Counting
ma è necessario implementare il rilevamento del ciclo da soli ... Urg
Ovviamente c'è questo weak_ptr
, ma purtroppo ho già visto perdite di memoria nonostante l'uso a shared_ptr
causa di quei cicli ... e quando ti trovi in un ambiente multi-thread, è estremamente difficile da rilevare!
4. Qual è la soluzione?
Non esiste un proiettile d'argento, ma come sempre è sicuramente fattibile. In assenza di GC è necessario essere chiari sulla proprietà:
- preferire avere un unico proprietario in un dato momento, se possibile
- in caso contrario, assicurati che il tuo diagramma di classe non abbia alcun ciclo pertinente alla proprietà e spezzali con una sottile applicazione di
weak_ptr
Quindi, in effetti, sarebbe bello avere un GC ... comunque non è un problema banale. E nel frattempo, dobbiamo solo rimboccarci le maniche.