2014-04-22 48 views
30

Stavo solo testando guardando attraverso un codice e ho notato qualcosa di simile a:Perché utilizzare una funzione anziché un riferimento al membro?

template<typename T> 
class example{ 
    public: 
     example(T t): m_value{t}{} 

     const T &value = m_value; 

    private: 
     T m_value; 
}; 

non ho mai visto prima. Quasi ogni API o una libreria ho usato prima definisce una funzione che restituisce una variabile membro in questo modo, e non una costante riferimento ad esso:

template<typename T> 
class example{ 
    public: 
     example(T t): m_value{t}{} 

     const T &value() const{ 
      return m_value; 
     } 

    private: 
     T m_value; 
}; 

Perché è il primo modo meno comune? Quali sono gli svantaggi?

+0

è solo stile. alcune persone odiano qualsiasi tipo di membro pubblico e alcuni pensano se siano immutabili/const –

+0

Una delle principali differenze è che nella seconda versione, il chiamante non può usare il riferimento per modificare 'm_value'. E sarebbe facile aggiornare la funzione per eseguire alcune azioni di controllo o pre-acquisizione. E prima di C++ 11 dovresti iniziare il riferimento in tutti i costruttori, il che è noioso. –

+0

Inoltre, il secondo risultato è che gli oggetti sono più piccoli –

risposta

34

ci sono alcune ragioni (linea) funzioni che restituiscono un adeguato riferimento sono migliori:

  1. Un riferimento richiede memoria (tipicamente la stessa quantità come un puntatore) in ogni oggetto

  2. I riferimenti in genere hanno lo stesso allineamento dei puntatori, pertanto l'oggetto circostante potrebbe richiedere un allineamento più elevato e quindi sprecare ancora più memoria

  3. Inizializzazione di un riferimento res (una minuscola quantità di) tempo

  4. Avere un campo membro di tipo riferimento disattiverà la copia predefinita e spostare operatori di assegnazione poiché i riferimenti non sono reseatable

  5. Avere un campo membro di tipo riferimento causerà la automaticamente copia predefinito generato e spostare i costruttori che non è corretto, dal momento che ora contengono i riferimenti ai membri di altri oggetti

  6. Funzioni può fare una verifica supplementare, come la verifica invarianti nel build di debug

Si noti che a causa dell'inallineamento, la funzione di solito non comporta costi aggiuntivi oltre a un binario potenzialmente leggermente più grande.

+1

Richiedere più memoria per la stessa funzionalità non è solitamente richiesto? –

+3

@Deduplicator 'const T & value = m_value;' è una variabile membro di tipo 'T const &'? –

+1

Penso che il punto numero 4 sia grande. Anche se potrebbe essere desiderabile ... – CoffeeandCode

2

La prima opzione richiede memoria aggiuntiva, proprio come un puntatore.

Se lo fai:

inline const T& value() const{ 
     return m_value; 
} 

avete lo stesso beneficio che il primo approccio, senza la necessità di memoria aggiuntiva.

Inoltre, poiché il primo approccio richiede C++ 11, sarà meno probabile che le persone lo usino.

+0

Non avresti bisogno della memoria richiesta per un riferimento quando ne hai restituito uno? – CoffeeandCode

+0

@CoffeeandCode: solo sullo stack per il secondo che la funzione viene eseguita. La prima versione ha quel riferimento all'interno di ogni istanza di ogni oggetto in ogni momento. –

+0

@CoffeeandCode Inoltre, poiché è inlineabile, il compilatore può ottimizzare e non utilizzare memoria extra, accedendo direttamente al membro dell'oggetto. Con il primo approccio questo tipo di ottimizzazione non è possibile perché il compilatore non può prevedere (almeno non facilmente) che il riferimento si riferisca a quel membro effettivo. –

1

Uso generale per la restituzione di riferimenti const:
Il ritorno di un riferimento costante è comune nei casi in cui la creazione o la distruzione di una copia è costosa.
In generale, la regola è la seguente: se il passaggio e l'utilizzo di un riferimento sono più costosi rispetto alla creazione di una copia, non farlo. Se non ti è stato chiesto un subobject, normalmente è addirittura impossibile.
Altrimenti, è un ottimizzazione delle prestazioni valida per restituire riferimenti costanti, che è trasparente a codice sorgente ben educato.
Molto codice template restituisce riferimenti costanti anche dove il test sopra non indica, solo per trattare tutte le specializzazioni allo stesso modo e perché la funzione è veramente piccola e quasi è comunque garantito che sia inline.

Ora, per la carne del vostro trovo curioso (mai visto il suo come ancora):
+ Non c'è bisogno di una funzione di accesso (ancora, questo è zoppo, sarà compilato fuori comunque)
- L'oggetto è più grande (i riferimenti normalmente richiedono tanto spazio quanto i puntatori)
- Potenzialmente è necessario un allineamento migliore a causa del punto precedente.
- Non ha funzioni membro magiche, perché il compilatore non sa come copiare i riferimenti
- Per le build di debug, non è possibile aggiungere ulteriori controlli.

Lo stesso sguardo & sentono senza danni collaterali che possono essere raggiunti risultati simili indipendente tra (solo un'ulteriore verifica per soggiorno di debug impossibile):

template<typename T> 
struct example { 
    example(T t): value{t}{} 
    union{ 
     const T value; 
     struct{ 
     private: 
      T value; 
      friend class example<T>; 
     } _value; 
    }; 
}; 
3

incapsulamento.

Per i puristi di programmazione orientata agli oggetti, m_value è un dettaglio di implementazione. I consumatori della classe example devono poter utilizzare un'unica interfaccia affidabile per accedere a value() e non devono dipendere dal modo in cui si verifica example per determinarlo. Una versione futura di example (o una specializzazione di modello complicata) potrebbe voler utilizzare la memorizzazione nella cache o la registrazione prima di restituire un value(); oppure potrebbe essere necessario calcolare immediatamente value() a causa di vincoli di memoria.

Se non si utilizza in origine una funzione di accesso, tutto ciò che utilizza example potrebbe essere necessario modificare se si cambia idea in seguito. E questo può introdurre tutti i tipi di rifiuti e bug. È più semplice estrapolarlo ulteriormente fornendo un accesso come value().

D'altra parte, alcune persone non sono così rigide riguardo a questi tipi di principi OOP e proprio come scrivere un codice che sia efficiente e leggibile, trattando il refactoring quando accade.

Problemi correlati