c++ pass array - Quando utilizzare riferimenti o puntatori





9 Answers

Le prestazioni sono esattamente le stesse, poiché i riferimenti sono implementati internamente come indicatori. Quindi non devi preoccuparti di questo.

Non esiste una convenzione generalmente accettata in merito a quando utilizzare riferimenti e puntatori. In alcuni casi devi restituire o accettare riferimenti (ad esempio, costruttore di copie), ma a parte questo sei libero di fare ciò che desideri. Una convenzione piuttosto comune che ho incontrato è quella di usare riferimenti quando il parametro deve riferirsi a un oggetto esistente e puntatori quando un valore NULL è ok.

Alcune convenzioni di codifica (come quelle di Google's ) prescrivono che si dovrebbero sempre usare puntatori o riferimenti const, perché i riferimenti hanno un po 'di sintassi poco chiara: hanno un comportamento di riferimento ma una sintassi del valore.

by reference ++>

Comprendo la sintassi e la semantica generale dei puntatori rispetto ai riferimenti, ma come dovrei decidere quando è più o meno appropriato utilizzare riferimenti o puntatori in un'API?

Naturalmente alcune situazioni richiedono l'una o l'altra (l' operator++ bisogno di un argomento di riferimento), ma in generale sto trovando che preferisco usare puntatori (e puntatori const) poiché la sintassi è chiara che le variabili vengono passate in modo distruttivo.

Ad esempio nel seguente codice:

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

Con il puntatore, è sempre (più) ovvio cosa sta succedendo, quindi per le API e simili dove la chiarezza è una grande preoccupazione sono i puntatori non più appropriati dei riferimenti? Ciò significa che i riferimenti dovrebbero essere usati solo quando necessario (ad es. operator++ )? Ci sono problemi di prestazioni con uno o l'altro?

MODIFICA (AGGIORNATO):

Oltre a consentire i valori NULL e la gestione degli array raw, sembra che la scelta dipenda dalle preferenze personali. Ho accettato la risposta qui sotto che fa riferimento alla Guida allo stile C ++ di Google , in quanto presentano la vista secondo cui "I riferimenti possono essere fonte di confusione, poiché hanno una sintassi del valore ma la semantica del puntatore.".

A causa del lavoro aggiuntivo necessario per disinfettare gli argomenti del puntatore che non dovrebbero essere NULL (ad esempio add_one(0) chiamerà la versione del puntatore e si interromperà durante il runtime), ha senso dal punto di vista della manutenibilità usare i riferimenti dove un oggetto DEVE essere presente, sebbene è un peccato perdere la chiarezza sintattica.




La mia regola generale è:

  • Utilizzare i puntatori per i parametri di uscita o di entrata / uscita. Quindi si può vedere che il valore sarà cambiato. (Devi usare & )
  • Utilizzare i puntatori se il parametro NULL è un valore accettabile. (Assicurati che sia const se è un parametro in entrata)
  • Utilizzare i riferimenti per il parametro in entrata se non può essere NULL e non è un tipo primitivo ( const T& ).
  • Usa puntatori o puntatori intelligenti quando restituisci un oggetto appena creato.
  • Usa puntatori o puntatori intelligenti come struct o membri della classe anziché riferimenti.
  • Utilizza i riferimenti per l'aliasing (ad esempio int &current = someArray[i] )

Indipendentemente da quale utilizzi, non dimenticare di documentare le tue funzioni e il significato dei loro parametri se non sono ovvi.




Disclaimer: a parte il fatto che i riferimenti non possono essere NULL né "rimbalzo" (il che significa che non possono cambiare l'oggetto di cui sono l'alias), in realtà è una questione di gusti, quindi non ho intenzione di dire "questo è meglio".

Detto questo, non sono d'accordo con la tua ultima affermazione nel post, in quanto non credo che il codice perda chiarezza con i riferimenti. Nel tuo esempio,

add_one(&a);

potrebbe essere più chiaro di

add_one(a);

poiché sai che molto probabilmente il valore di a cambierà. D'altra parte, però, la firma della funzione

void add_one(int* const n);

in qualche modo non è chiaro: n sarà un singolo intero o un array? A volte hai accesso solo a intestazioni (scarsamente documentate) ea firme simili

foo(int* const a, int b);

non sono facili da interpretare a prima vista.

Imho, i riferimenti sono buoni come puntatori quando non è necessario (ri) allocare né ribattere (nel senso spiegato prima). Inoltre, se uno sviluppatore utilizza solo puntatori per gli array, le firme delle funzioni sono in qualche modo meno ambigue. Per non parlare del fatto che la sintassi degli operatori è molto più leggibile con i riferimenti.




Qualsiasi differenza di prestazioni sarebbe così piccola da non giustificare l'utilizzo di un approccio meno chiaro.

Innanzitutto, un caso che non è stato menzionato in cui i riferimenti sono generalmente superiori sono i riferimenti const . Per i tipi non semplici, il passaggio di un const reference evita di creare un elemento temporaneo e non causa la confusione di cui si è preoccupati (poiché il valore non viene modificato). Qui, forzare una persona a passare un puntatore causa la stessa confusione di cui sei preoccupato, poiché vedere l'indirizzo preso e passato a una funzione potrebbe farti pensare che il valore sia cambiato.

In ogni caso, sostanzialmente sono d'accordo con te. Non mi piacciono le funzioni che prendono riferimenti per modificare il loro valore quando non è molto ovvio che questo è ciò che sta facendo la funzione. Anch'io preferisco usare i puntatori in quel caso.

Quando hai bisogno di restituire un valore in un tipo complesso, tendo a preferire i riferimenti. Per esempio:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

Qui, il nome della funzione rende chiaro che stai ricevendo informazioni in un array. Quindi non c'è confusione.

I principali vantaggi dei riferimenti sono che contengono sempre un valore valido, sono più puliti dei puntatori e supportano il polimorfismo senza richiedere alcuna sintassi aggiuntiva. Se nessuno di questi vantaggi si applica, non vi è alcun motivo per preferire un riferimento su un puntatore.




C'è un problema con la regola " usa riferimenti ove possibile " e si verifica se si desidera mantenere un riferimento per un ulteriore utilizzo. Per illustrare questo con l'esempio, immagina di avere le seguenti lezioni.

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

All'inizio può sembrare una buona idea avere un parametro nel RefPhone(const SimCard & card) passato da un riferimento, poiché impedisce di passare i puntatori errati / nulli al costruttore. In qualche modo incoraggia l'allocazione delle variabili in pila e traendo beneficio da RAII.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

Ma poi i temporari vengono a distruggere il tuo mondo felice.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

Quindi, se ci si attiene ciecamente ai riferimenti, si scambia la possibilità di passare dei puntatori non validi per la possibilità di memorizzare riferimenti a oggetti distrutti, che ha praticamente lo stesso effetto.

modifica: nota che ho aderito alla regola "Utilizza riferimenti ovunque sia possibile, puntatori ovunque sia necessario. Evita i puntatori finché non puoi". dalla risposta più upvoted e accettata (altre risposte suggeriscono anche così). Anche se dovrebbe essere ovvio, l'esempio non è mostrare che i riferimenti in quanto tali sono cattivi. Tuttavia possono essere utilizzati in modo improprio, proprio come i puntatori e possono portare le loro minacce al codice.

Ci sono seguenti differenze tra puntatori e riferimenti.

  1. Quando si tratta di passare le variabili, passare per riferimento sembra passare per valore, ma ha una semantica del puntatore (si comporta come un puntatore).
  2. Il riferimento non può essere inizializzato direttamente su 0 (null).
  3. Riferimento (riferimento, oggetto non referenziato) non può essere modificato (equivalente al puntatore "* const").
  4. il riferimento const può accettare parametri temporanei.
  5. I riferimenti const locali prolungano la durata di oggetti temporanei

Tenendo conto di queste mie regole attuali sono le seguenti.

  • Utilizzare i riferimenti per i parametri che verranno utilizzati localmente nell'ambito di una funzione.
  • Utilizzare i puntatori quando 0 (null) è un valore di parametro accettabile o è necessario memorizzare i parametri per un ulteriore utilizzo. Se 0 (null) è accettabile, aggiungo il suffisso "_n" al parametro, uso il puntatore protetto (come QPointer in Qt) o semplicemente documentarlo. Puoi anche usare puntatori intelligenti. Devi essere ancora più attento con i puntatori condivisi rispetto ai normali puntatori (altrimenti potresti finire con perdite di memoria di progettazione e problemi di responsabilità).



Le seguenti sono alcune linee guida.

Una funzione utilizza i dati passati senza modificarli:

  1. Se l'oggetto dati è piccolo, ad esempio un tipo di dati incorporato o una piccola struttura, passarlo per valore.

  2. Se l'oggetto dati è un array, usa un puntatore perché è la tua unica scelta. Rendi il puntatore un puntatore a const.

  3. Se l'oggetto dati è una struttura di buone dimensioni, utilizzare un puntatore const o un riferimento const per aumentare l'efficienza del programma. Si risparmia il tempo e lo spazio necessari per copiare una struttura o un progetto di classe. Crea il puntatore o il riferimento const.

  4. Se l'oggetto dati è un oggetto di classe, usa un riferimento const. La semantica del design di classe spesso richiede l'uso di un riferimento, che è il motivo principale per cui C ++ ha aggiunto questa funzione. Quindi, il metodo standard per passare gli argomenti oggetto di classe è per riferimento.

Una funzione modifica i dati nella funzione di chiamata:

1.Se l'oggetto dati è un tipo di dati incorporato, utilizzare un puntatore. Se si individuano codice come fixit (& x), dove x è un int, è abbastanza chiaro che questa funzione intende modificare x.

2.Se l'oggetto dati è un array, utilizzare l'unica opzione: un puntatore.

3.Se l'oggetto dati è una struttura, utilizzare un riferimento o un puntatore.

4.Se l'oggetto dati è un oggetto di classe, utilizzare un riferimento.

Naturalmente, queste sono solo linee guida e potrebbero esserci motivi per fare scelte diverse. Ad esempio, cin usa i riferimenti per i tipi di base in modo da poter usare cin >> n invece di cin >> & n.




In generale una variabile membro non dovrebbe mai essere un riferimento perché non ha senso in questo. Fa sì che la classe non sia assegnabile se non si fornisce un operatore di assegnazione. Inoltre, una volta impostato il riferimento del membro per fare riferimento ad un oggetto, non è possibile cambiarlo per fare riferimento a un altro oggetto. L'utilizzo più appropriato di un riferimento viene utilizzato come parametro di funzione che abilita il passaggio per riferimento.




Puntatori e riferimenti hanno la stessa velocità e i puntatori possono fare tutto ciò che può fare riferimento e altro. È fortemente basato sull'opinione personale quale usare. È comune utilizzare i riferimenti a meno che non si abbia realmente bisogno di alcune funzionalità dei puntatori.




Preferisco usare i puntatori. Almeno è chiaro cosa stai facendo. Ho la sensazione che i riferimenti siano usati principalmente a causa di STL e delle sue implicazioni sulla sintassi sul codice. A causa di ciò anche tante novità della libreria standard C ++ come std :: move ..... per ottenere esattamente ciò che si desidera, e non ciò a cui si sarebbe pensato in modo intuitivo.




Related