2010-11-20 17 views
6

Nel mio lavoro giorno per giorno, mi ritrovo spesso a scrivere classi come in questo esempio semplificato:Class - come fornire accesso in sola lettura al contenitore di membro della classe

class CGarage 
{ 
public: 
    CGarage(); 
    ~CGarage(); 
    typedef std::vector<Car> CarCollection; 

private: 
    CarCollection m_Cars; 
}; 

voglio gli utenti del CGarage per avere accesso in sola lettura a CarCollection. Al fine di raggiungere tale obiettivo, quelli sono alcune soluzioni comuni che non sono tutte molto soddisfacenti:

Soluzione 1

class CGarage 
{ 
    Car GetCar(CarCollection::size_type index) const; 
    CarCollection::size_type CarCount() const; 
}; 

svantaggio principale:

  • Mancando iteratori, non posso utilizzare gli algoritmi STL su Cars (ad es. for_each (...))

Solut ione 2

class CGarage 
{ 
    CarCollection::const_iterator CarBegin() const; 
    CarCollection::const_iterator CarEnd() const; 
    CarCollection::size_type CarCount() const; 
}; 

svantaggio principale:

  • Un sacco di codice boilerplate se avete bisogno di supporto per altri tipi di iteratori (che, reverse_it).

Soluzione 3

class CGarage 
{ 
    const CarCollection GetCars() const; 
}; 

svantaggio principale:

  • costo di copiare CarCollection al ritorno per valore
  • dettagli di implementazione noti agli utenti della classe (ad esempio, non può cambiare per std :: lista senza modifica del codice utente di rottura)

Soluzione 4

class CGarage 
{ 
    const CarCollection& GetCars() const; 
}; 

svantaggio principale:

  • vita di riferimento CarCollection legata alla vita di CGarage
  • dettagli
  • implementazione noti agli utenti della classe

Domande

Come si dovrebbero fornire accesso in sola lettura al CarCollection?

La soluzione cambia, se CarCollection è un vettore con indicazioni per Car?

Se si consente l'accesso in lettura e scrittura alla raccolta, è accettabile rendere pubblica la raccolta?

Grazie per qualsiasi consiglio

risposta

5

Come forniresti l'accesso in sola lettura a CarCollection?

Non vedo cosa c'è di sbagliato nella soluzione 4. Dovrebbe essere ovvio per gli utenti di CGarage che un riferimento alla propria collezione di auto è legato alla durata del garage. Se hanno bisogno della collezione di auto per sopravvivere nel garage, possono sempre prenderne una copia, se lo desiderano.

In alternativa, fare in modo che CGarage tenga un shared_ptr alla raccolta auto e restituirlo, ma non lo consiglierei.

La soluzione cambia, se CarCollection è un vettore con indicazioni per Car?

Per le raccolte di oggetti di proprietà (ad esempio i tipi di riferimento), è preferibile utilizzare un contenitore diverso. I contenitori std:: sono tutti progettati per tipi di valore e non gestiscono molto bene i tipi di riferimento (in particolare la costanza). Per questi, usa qualcosa come Boost's ptr_vector.

Se si consente l'accesso in lettura e scrittura alla raccolta, è accettabile rendere pubblica la raccolta?

A seconda della situazione specifica. La semantica della collezione potrebbe cambiare? In caso contrario, puoi tranquillamente renderlo pubblico (ad esempio std::pair). Non consiglierei di farlo per problemi specifici del dominio.

+0

Peter, grazie per la tua risposta dettagliata. Una domanda: usare la soluzione 4 e un contenitore std :: vector . Come si evita all'utente di cambiare l'auto (ha puntatori non costanti alle macchine reali, quindi può cambiarle)? – nabulke

+1

In questo caso è necessario utilizzare qualcosa come boost 'ptr_vector' (http://www.boost.org/doc/libs/1_45_0/libs/ptr_container/doc/ptr_vector.html), vedere anche (http: // www .codeproject.com/KB/stl/ptr_vecto.aspx) –

0

posso rispondere a uno:

se si consente di leggere e scrivere l'accesso alla collezione, è accettabile per rendere la collezione pubblica?

Non necessariamente. Che cosa succede se è necessario modificare l'implementazione oppure è necessario eseguire una parte di codice ogni volta che viene modificata la variabile. E anche se non lo fai, potrebbe essere necessario in futuro, e facendo una modifica da una var pubblica a un set/get puoi rompere un sacco di codice.

+0

Sono d'accordo. Ma come implementeresti l'accesso in lettura/scrittura senza mostrare i dettagli di implementazione del contenitore? – nabulke

0

Opzione 4, ma restituire un riferimento a una classe di base astratta che include operatori di conversione espliciti in raccolte standard. Ciò ridurrà almeno il numero di dettagli di implementazione esposti.

Per ridurre ulteriormente la dipendenza dai dettagli di implementazione, convertire la classe che contiene la raccolta in un modello, parametrizzando il tipo di raccolta.

1

non sarebbe sufficiente dichiararlo così?

const CarCollection& Cars() { return m_Cars; } 

poi

CarCollection::const_iterator it = garage.Cars().begin(); 

dovrebbe funzionare ma

CarCollection::iterator it = garage.Cars().begin(); 

darebbe errore.

1

Vorrei andare con la soluzione . Per quanto riguarda il problema della costanza per i contenitori di puntatori, è possibile utilizzare boost::ptr_vector che propaga correttamente la costanza (tra le altre cose).

+0

"Propaga correttamente constness" significa che se restituisco un CarCollection const o un CarCollection const e il puntatore punterà a un oggetto const car (anche se la raccolta è definita come std :: vector >)? – nabulke

+0

@nabulke yes, ma è boost :: ptr_vector , e fai attenzione, non è l'unica differenza con un vettore std :: (ptr_vector ha la proprietà dei puntatori memorizzati) – icecrime

1

Dipende.

  1. Se i clienti della classe hanno bisogno di un vettore di automobili, ad es. presumono che le macchine siano memorizzate in modo contiguo nella memoria per qualsiasi motivo (non vedo però un altro motivo), quindi dovresti fornire una funzione membro che restituisca un riferimento costante a un vettore. Non preoccuparti del passaggio per valore e dei problemi di eccezione relativi alla sicurezza associati alla creazione dell'oggetto. Se il vettore deve sopravvivere all'oggetto Garage, il client ne farà una copia.

  2. Ora, in ogni altro caso, il principio di accoppiamento minimo suggerisce di restituire una coppia di iteratori. Dopo tutto, una coppia di iteratori è l'espressione generica di una collezione . Se il client della classe richiede assunzioni di complessità, restituire il giusto tipo di iteratori (e documentarlo).

  3. Per comodità, e solo quando ha senso, è possibile sovraccaricare operator[]. Un garage è una collezione di macchine. Se le aree di parcheggio all'interno del garage sono numerate e si desidera accedere alle auto tramite slot, può essere una buona soluzione.

Tuttavia, hai ragione nel dire che iteratori C++ sono stati progettati in un modo che ti costringe a scrivere un sacco di codice boilerplate. Quindi, quando si scrive la classe per la prima volta, restituire un riferimento const a un vettore e quindi un refactoring.

Problemi correlati