Il vantaggio di utilizzare std::unique_ptr<T>
(parte non dover ricordare chiamare delete
o delete[]
esplicitamente) è che garantisce che un puntatore o è nullptr
o punta a un'istanza valida dell'oggetto (base).Tornerò su questo dopo aver risposto alla tua domanda, ma il primo messaggio è DO usa puntatori intelligenti per gestire la durata di oggetti allocati dinamicamente.
Ora, il problema è in realtà come utilizzare questo con il vostro vecchio codice.
mio suggerimento è che se non si desidera trasferire o condividere la proprietà, si dovrebbe sempre passa riferimenti all'oggetto. Dichiarare la funzione come questo (con o senza const
qualificazioni, se necessario):
bool func(BaseClass& ref, int other_arg) { ... }
Poi il chiamante, che ha un std::shared_ptr<BaseClass> ptr
sarà o gestire il caso nullptr
oppure chiederà bool func(...)
per calcolare il risultato:
if (ptr) {
result = func(*ptr, some_int);
} else {
/* the object was, for some reason, either not created or destroyed */
}
Ciò significa che qualsiasi chiamante deve promettere che il riferimento è valido e che continuerà a essere valido durante l'esecuzione del corpo della funzione.
Ecco il motivo per cui credo fermamente si dovrebbe non passaggio puntatori prime o riferimenti a puntatori intelligenti.
Un puntatore non elaborato è solo un indirizzo di memoria. Può avere uno dei (almeno) 4 significati:
- L'indirizzo di un blocco di memoria in cui si trova l'oggetto desiderato. (il buono)
- L'indirizzo 0x0 di cui si può essere certi non è dereferenziabile e potrebbe avere la semantica di "niente" o "nessun oggetto". (the bad)
- L'indirizzo di un blocco di memoria che si trova al di fuori dello spazio indirizzabile del processo (il dereferenziamento potrebbe causare l'arresto anomalo del programma). (il brutto)
- L'indirizzo di un blocco di memoria che può essere dereferenziato ma che non contiene ciò che ci si aspetta. Forse il puntatore è stato modificato per errore e ora punta a un altro indirizzo scrivibile (di una variabile completamente diversa all'interno del processo). Scrivere in questa posizione di memoria farà sì che si verifichi un sacco di divertimento, a volte, durante l'esecuzione, perché il sistema operativo non si lamenterà fino a quando ti è permesso di scrivere lì. (Zoinks!)
correttamente utilizzando puntatori intelligenti allevia i casi piuttosto spaventosi 3 e 4, che di solito non sono rilevabili in fase di compilazione, e che in genere solo l'esperienza in fase di esecuzione quando il tuo programma va in crash o fa cose inaspettate.
Passando puntatori intelligenti come argomenti ha due svantaggi: non è possibile modificare la const
-ness del sottolineato oggetto senza fare una copia (che aggiunge overhead per shared_ptr
e non è possibile per unique_ptr
), e vi sono ancora a sinistra con il secondo significato (nullptr
).
Ho contrassegnato il secondo caso come (il cattivo) dal punto di vista del design. Questa è una discussione più sottile sulla responsabilità.
Immaginate cosa significa quando una funzione riceve un parametro come nullptr
. Prima deve decidere cosa farne: usare un valore "magico" al posto dell'oggetto mancante? cambiare completamente il comportamento e calcolare qualcos'altro (che non richiede l'oggetto)? prendere dal panico e fare un'eccezione? Inoltre, cosa succede quando la funzione prende 2, o 3 o anche più argomenti con il puntatore raw? Deve controllare ciascuno di essi e adattare il suo comportamento di conseguenza. Questo aggiunge un nuovo livello alla convalida dell'input senza un vero motivo.
Il chiamante dovrebbe essere quello con sufficienti informazioni contestuali per prendere queste decisioni, o, in altre parole, il male è meno spaventoso più si sa. La funzione, d'altra parte, dovrebbe solo prendere la promessa del chiamante che la memoria a cui è puntata è sicura per funzionare come previsto. (I riferimenti sono ancora indirizzi di memoria, ma concettualmente rappresentano una promessa di validità.)
Questo è bello, ma la cam si libera di 'std :: unique_ptr' per un argomento' std :: vector '? –