2011-11-04 11 views
35

Si dice che di seguito sia meglio avere il primo/secondo come membri pubblici. Credo che sia quasi così male. Se stai dando un modo per accedere a una variabile privata al di fuori della classe, allora qual è il punto? Qualora non siano le funzioniI riferimenti di ritorno delle variabili membro sono una cattiva pratica?

T First(); void(or T) First(const T&) 

Esempio:

// Example 17-3(b): Proper encapsulation, initially with inline accessors. Later 
// in life, these might grow into nontrivial functions if needed; if not, then not. 
// 
template<class T, class U> 
class Couple { 
    Couple()   : deleted_(false) { } 
    T& First()   { return first_; } 
    U& Second()  { return second_; } 
    void MarkDeleted() { deleted_ = true; } 
    bool IsDeleted() { return deleted_; } 

private: 
T first_; 
U second_; 
bool deleted_; 
}; 

risposta

43

Ci sono diversi motivi per cui restituire riferimenti (o puntatori) ai componenti interni di una classe sono errati. A partire da (quello che io ritengo essere) il più importante:

  1. incapsulamento è violato: Hai perdite un dettaglio di implementazione, il che significa che non è più possibile modificare le parti interne di classe come si desidera. Se hai deciso di non memorizzare first_ ad esempio, ma per calcolarlo al volo, come restituiresti un riferimento? Non puoi, quindi sei bloccato.

  2. Invariant non sono più sostenibili (in caso di riferimento non const): chiunque può accedere e modificare l'attributo di cui a volontà, quindi non è possibile "controllare" i suoi cambiamenti. Significa che non puoi mantenere un invariante di cui questo attributo è parte. In sostanza, la tua classe si sta trasformando in un blob.

  3. Lifetime problemi di avviamento: è facile mantenere un riferimento o un puntatore all'attributo dopo che l'oggetto originale a cui appartengono ha cessato di esistere. Questo è ovviamente un comportamento indefinito. La maggior parte dei compilatori tenterà di mettere in guardia sul mantenimento dei riferimenti agli oggetti nello stack, ad esempio, ma non conosco alcun compilatore che sia riuscito a produrre tali avvisi per i riferimenti restituiti da funzioni o metodi: sei da solo.

Come tale, di solito è meglio non dare riferimenti o riferimenti agli attributi. Neanche costosi!

Per piccoli valori, è generalmente sufficiente passarli copiando (sia in e out), specialmente ora con la semantica di movimento (sul fondo).

Per valori più grandi, dipende molto dalla situazione, a volte un proxy può alleviare i tuoi problemi.

Infine, si noti che per alcune classi, avere membri pubblici non è così male.Quale sarebbe il punto di incapsulare i membri di un pair? Quando ti ritrovi a scrivere una classe che non è altro che una collezione di attributi (non invariante di sorta), allora invece di prendere tutto OO su di noi e scrivere una coppia getter/setter per ognuno di essi, considera la possibilità di renderli pubblici.

+2

+1, ma esiste una controproposta logica per fornire gli accessor: è possibile aggiungere una strumentazione alla chiamata e questo rende più facile trovare i luoghi in cui l'oggetto viene aggiornato nel codice o collegare un debugger per rilevare i problemi. –

+1

Per informazioni, l'esempio parlava di classi che hanno membri privati ​​quindi 'bool deleted_;'. Mi piace POD –

+0

@David: non sono contro accessors come in 'T get()/void set (T)', in cui puoi tracciare efficacemente i cambiamenti dei tuoi valori, fornire il calcolo lazy e molte altre cose, ma il solo un interessante tidbit in un 'T & access()' significa "tracciare" che è stato effettuato un accesso, il che non porta molto alla tabella. –

17

Se template tipi T e U sono grandi strutture poi ritorno per valore è costoso. Tuttavia, sei corretto che restituire per riferimento equivale a dare accesso a una variabile private. Per risolvere entrambe le questioni, renderli const riferimenti:

const T& First() const { return first_; } 
const U& Second() const { return second_; } 

P.S. Inoltre, è una cattiva pratica mantenere le variabili non inizializzate all'interno del costruttore, quando non esiste un metodo setter. Sembra che nel codice originale, First() e Second() siano wrapper su first_ e second_ che erano pensati per leggere/scrivere entrambi.

+0

ah ah, buona soluzione. Finché legge ('Primo()') ha effetti collaterali tali che sarebbe completamente soddisfacente (non una limitazione irragionevole, ma ancora una restrizione :)) –

+2

+1. Nice ........ – Nawaz

+3

[Questo link] (http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/) ha alcuni spunti interessanti su come valutare e valori R. – juanchopanza

6

La risposta dipende da ciò che si sta tentando di fare. I riferimenti di ritorno sono un modo conveniente per facilitare la mutazione delle strutture di dati. Un buon esempio è la mappa di stl. Esso restituisce riferimento all'elemento cioè

std::map<int,std::string> a; 
a[1] = 1; 

nulla che impedisca di fare

auto & aref = a[1]; 

È necessariamente una cattiva pratica? Non lo penserei. Direi che se ne puoi fare a meno lo fai. Se rende la vita più comoda ed efficiente usala e sii consapevole di ciò che stai facendo.