La maggior parte delle risposte qui non riesce a rispondere all'ambiguità intrinseca nell'avere un puntatore non elaborato nella firma di una funzione, in termini di espressione dell'intento. I problemi sono i seguenti:
Il chiamante non sa se il puntatore punta a un singolo oggetto o all'inizio di un "array" di oggetti.
Il chiamante non sa se il puntatore "possiede" la memoria a cui punta. IE, indipendentemente dal fatto che la funzione debba liberare la memoria. ( foo(new int)
- È una perdita di memoria?).
Il chiamante non sa se è nullptr
possibile passare in sicurezza alla funzione.
Tutti questi problemi sono risolti da riferimenti:
I riferimenti si riferiscono sempre a un singolo oggetto.
I riferimenti non possiedono mai la memoria a cui si riferiscono, sono semplicemente una vista nella memoria.
I riferimenti non possono essere nulli.
Ciò rende i riferimenti un candidato molto migliore per l'uso generale. Tuttavia, i riferimenti non sono perfetti: ci sono un paio di problemi importanti da considerare.
- Nessuna indiretta esplicita. Questo non è un problema con un puntatore non elaborato, poiché dobbiamo usare l'
&
operatore per dimostrare che stiamo effettivamente passando un puntatore. Ad esempio, int a = 5; foo(a);
qui non è affatto chiaro che a sia passato per riferimento e possa essere modificato.
- Valori Null. Questa debolezza dei puntatori può anche essere un punto di forza, quando in realtà vogliamo che i nostri riferimenti siano nulli. Visto che
std::optional<T&>
non è valido (per buoni motivi), i puntatori ci danno la nullità che desideri.
Quindi sembra che quando vogliamo un riferimento nullabile con l'indirizzamento esplicito, dovremmo cercare un T*
diritto? Sbagliato!
astrazioni
Nella nostra disperazione per la nullità, possiamo raggiungere T*
e semplicemente ignorare tutte le carenze e le ambiguità semantiche elencate in precedenza. Invece, dovremmo cercare ciò che fa meglio C ++: un'astrazione. Se scriviamo semplicemente una classe che avvolge un puntatore, otteniamo l'espressività, così come il nullability e l'indirizzamento esplicito.
template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}
T& get() const {
return *ptr;
}
explicit operator bool() const {
return bool(ptr);
}
private:
T* ptr;
};
Questa è l'interfaccia più semplice che ho potuto inventare, ma fa il lavoro in modo efficace. Permette di inizializzare il riferimento, controllando se esiste un valore e accedendo al valore. Possiamo usarlo così:
void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}
int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
Abbiamo raggiunto i nostri obiettivi! Vediamo ora i vantaggi, rispetto al puntatore non elaborato.
- L'interfaccia mostra chiaramente che il riferimento dovrebbe riferirsi solo a un oggetto.
- Chiaramente non possiede la memoria a cui fa riferimento, in quanto non ha un distruttore definito dall'utente e nessun metodo per eliminare la memoria.
- Il chiamante sa che
nullptr
può essere passato, poiché l'autore della funzione chiede esplicitamente unoptional_ref
Da qui potremmo rendere l'interfaccia più complessa, come l'aggiunta di operatori di uguaglianza, una monade get_or
e map
un'interfaccia, un metodo che ottiene il valore o genera un'eccezione, constexpr
supporto. Questo può essere fatto da te.
In conclusione, invece di utilizzare i puntatori non elaborati, ragiona su cosa significano effettivamente quei puntatori nel tuo codice e sfrutta un'astrazione standard della libreria o scrivine una tua. Ciò migliorerà significativamente il tuo codice.
new
di creare un puntatore e i conseguenti problemi di proprietà.