Domanda

È meglio in C ++ passare per valore o passare per riferimento costante?

Mi chiedo quale sia la migliore pratica. Mi rendo conto che passare per riferimento costante dovrebbe fornire prestazioni migliori nel programma perché non si sta facendo una copia della variabile.

È stato utile?

Soluzione

In genere era consigliata la procedura consigliata 1 per utilizzare il passaggio per const ref per tutti i tipi , ad eccezione dei tipi predefiniti ( char , int , double , ecc.), per gli iteratori e per gli oggetti funzione (lambdas, classi derivanti da std :: * _ function ).

Ciò era particolarmente vero prima dell'esistenza di mossa semantica . Il motivo è semplice: se si passa per valore, è necessario creare una copia dell'oggetto e, ad eccezione di oggetti molto piccoli, è sempre più costoso del passaggio di un riferimento.

Con C ++ 11, abbiamo guadagnato sposta la semantica . In poche parole, spostare la semantica consente che, in alcuni casi, un oggetto possa essere passato & # 8220; per valore & # 8221; senza copiarlo. In particolare, questo è il caso in cui l'oggetto che si sta passando è un rvalue .

Di per sé, spostare un oggetto è comunque costoso almeno quanto passare per riferimento. Tuttavia, in molti casi una funzione copia comunque internamente un oggetto & # 8212; vale a dire che ci vorrà proprietà dell'argomento. 2

In queste situazioni abbiamo il seguente compromesso (semplificato):

  1. Possiamo passare l'oggetto per riferimento, quindi copiarlo internamente.
  2. Possiamo passare l'oggetto per valore.

& # 8220; Passa per valore & # 8221; causa comunque la copia dell'oggetto, a meno che l'oggetto non sia un valore. Nel caso di un valore, l'oggetto può invece essere spostato, in modo che il secondo caso non sia più improvvisamente & # 8220; copia, quindi sposta & # 8221; ma & # 8220; sposta, quindi (potenzialmente) sposta di nuovo & # 8221 ;.

Per oggetti di grandi dimensioni che implementano costruttori di mosse appropriati (come vettori, stringhe & # 8230;), il secondo caso è quindi ampiamente più efficiente del primo. Pertanto, si consiglia di utilizzare il passaggio per valore se la funzione diventa proprietaria dell'argomento e se il tipo di oggetto supporta uno spostamento efficiente .


Una nota storica:

In effetti, qualsiasi compilatore moderno dovrebbe essere in grado di capire quando passare per valore è costoso e, se possibile, convertire implicitamente la chiamata per utilizzare un riferimento const.

In teoria. In pratica, i compilatori non possono sempre cambiarlo senza interrompere l'interfaccia binaria della funzione. In alcuni casi speciali (quando la funzione è incorporata) la copia verrà effettivamente elusa se il compilatore riesce a capire che l'oggetto originale non sarà cambiato attraverso le azioni nella funzione.

Ma in generale il compilatore non può determinarlo, e l'avvento della semantica di spostamento in C ++ ha reso questa ottimizzazione molto meno rilevante.


1 Es. in Scott Meyers, C ++ efficace .

2 Questo è particolarmente vero per i costruttori di oggetti, che possono prendere argomenti e archiviarli internamente per far parte dello stato dell'oggetto costruito.

Altri suggerimenti

Modifica: Nuovo articolo di Dave Abrahams su cpp-next:

Vuoi velocità? Passa per valore.


Passa per valore per le strutture in cui la copia è economica ha l'ulteriore vantaggio che il compilatore può presumere che gli oggetti non siano alias (non sono gli stessi oggetti). Utilizzando il pass-by-reference il compilatore non può presumere che sempre. Esempio semplice:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

il compilatore può ottimizzarlo in

g.i = 15;
f->i = 2;

poiché sa che feg non condividono la stessa posizione. se g fosse un riferimento (foo & amp;), il compilatore non avrebbe potuto immaginarlo. poiché g.i potrebbe quindi essere aliasato da f- > i e deve avere un valore di 7. quindi il compilatore dovrebbe recuperare nuovamente il nuovo valore di g.i dalla memoria.

Per regole più pratiche, ecco un buon insieme di regole che si trovano nell'articolo Move Constructors ( lettura altamente raccomandata).

  • Se la funzione intende cambiare l'argomento come effetto collaterale, prendilo per riferimento non const.
  • Se la funzione non modifica il suo argomento e l'argomento è di tipo primitivo, prendilo per valore.
  • Altrimenti prendilo per riferimento const, tranne nei seguenti casi
    • Se la funzione avrebbe quindi bisogno di fare comunque una copia del riferimento const, prendilo per valore.

" Primitive " sopra significa tipi di dati sostanzialmente piccoli che sono lunghi pochi byte e non sono polimorfici (iteratori, oggetti funzione, ecc ...) o costosi da copiare. In quel documento, c'è un'altra regola. L'idea è che a volte si vuole fare una copia (nel caso in cui l'argomento non possa essere modificato), e a volte non si vuole (nel caso in cui si desideri utilizzare l'argomento stesso nella funzione se l'argomento era comunque temporaneo , per esempio). L'articolo spiega in dettaglio come è possibile farlo. In C ++ 1x tale tecnica può essere utilizzata in modo nativo con il supporto del linguaggio. Fino ad allora, andrei con le regole di cui sopra.

Esempi: per rendere una stringa maiuscola e restituire la versione maiuscola, si dovrebbe sempre passare per valore: si deve comunque prenderne una copia (non si può cambiare direttamente il riferimento const) - quindi meglio renderlo trasparente il più possibile al chiamante e crea quella copia in anticipo in modo che il chiamante possa ottimizzare il più possibile - come dettagliato in quel documento:

my::string uppercase(my::string s) { /* change s and return it */ }

Tuttavia, se non è necessario modificare comunque il parametro, prenderlo facendo riferimento a const:

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

Tuttavia, se lo scopo del parametro è scrivere qualcosa nell'argomento, passarlo per riferimento non const

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}

Dipende dal tipo. Stai aggiungendo il piccolo sovraccarico di dover fare un riferimento e una dereferenza. Per i tipi con dimensioni uguali o inferiori ai puntatori che utilizzano il ctor copia predefinito, probabilmente sarebbe più veloce passare per valore.

Come è stato sottolineato, dipende dal tipo. Per i tipi di dati integrati, è meglio passare per valore. Anche alcune strutture molto piccole, come una coppia di ints, possono funzionare meglio passando per valore.

Ecco un esempio, supponi di avere un valore intero e desideri passarlo a un'altra routine. Se quel valore è stato ottimizzato per essere archiviato in un registro, quindi se si desidera passarlo come riferimento, deve prima essere memorizzato e quindi un puntatore a quella memoria posizionata nello stack per eseguire la chiamata. Se veniva passato per valore, tutto ciò che è richiesto è il registro inserito nello stack. (I dettagli sono un po 'più complicati di quelli dati da diversi sistemi di chiamata e CPU).

Se stai programmando un template, di solito sei costretto a passare sempre da const ref poiché non conosci i tipi che vengono passati. Passare le penalità per aver passato qualcosa di cattivo in valore è molto peggio delle penalità del passare un build -in type di const ref.

Sembra che tu abbia la tua risposta. Il passaggio per valore è costoso, ma ti dà una copia con cui lavorare se ne hai bisogno.

Questo è ciò su cui lavoro normalmente durante la progettazione dell'interfaccia di una funzione non modello:

  1. Passa per valore se la funzione non vuole modificare il parametro e il il valore è economico da copiare (int, double, float, char, bool, ecc ... Notare che std :: string, std :: vector e il resto dei contenitori nella libreria standard NON sono)

  2. Passa dal puntatore const se il valore è costoso da copiare e la funzione lo fa non desidera modificare il valore indicato e NULL è un valore gestito dalla funzione.

  3. Passa dal puntatore non const se il valore è costoso da copiare e la funzione desidera modificare il valore indicato e NULL è un valore gestito dalla funzione.

  4. Passa per riferimento const quando il valore è costoso da copiare e la funzione non vuole modificare il valore a cui fa riferimento e NULL non sarebbe un valore valido se invece fosse usato un puntatore.

  5. Passa per riferimento non const quando il valore è costoso da copiare e la funzione vuole modificare il valore a cui fa riferimento e NULL non sarebbe un valore valido se invece fosse usato un puntatore.

Di norma è meglio passare per riferimento const. Ma se è necessario modificare l'argomento della funzione localmente, è meglio utilizzare il passaggio per valore. Per alcuni tipi di base le prestazioni in generale sono le stesse sia per il passaggio per valore che per riferimento. In realtà riferimento internamente rappresentato dal puntatore, ecco perché ci si può aspettare, ad esempio, che per il puntatore sia il passaggio sia lo stesso in termini di prestazioni, o anche il passaggio in base al valore può essere più veloce a causa della inutile dereferenza.

Come regola empirica, valore per i tipi non di classe e riferimento const per le classi. Se una classe è veramente piccola, probabilmente è meglio passare per valore, ma la differenza è minima. Quello che vuoi davvero evitare è passare una classe gigantesca in base al valore e averlo tutto duplicato: questo farà un'enorme differenza se stai passando, diciamo, uno std :: vector con alcuni elementi al suo interno.

Passa per valore per tipi piccoli.

Passa per i riferimenti const per i grandi tipi (la definizione di big può variare tra macchine) MA, in C ++ 11, passa per valore se stai per consumare i dati, poiché puoi sfruttare la semantica di spostamento. Ad esempio:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

Ora il codice chiamante farebbe:

Person p(std::string("Albert"));

E solo un oggetto verrebbe creato e spostato direttamente nel membro name_ nella classe Person . Se passi per riferimento const, dovrai crearne una copia per metterlo in name_ .

Differenza semplice: - Nella funzione abbiamo i parametri di input e output, quindi se il tuo parametro di input e output di passaggio è lo stesso, usa call by reference else se i parametri di input e output sono diversi, quindi è meglio usare call per value.

esempio importo nullo (int account, int deposit, int total)

parametro di input: conto, deposito parametro di output: totale

input e out è diverso usa call by vaule

  1. importo nullo (int totale, deposito int)

inserisci deposito totale totale di output

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top