Domanda

In C++, che alternative ho per esporre una collezione che, dal punto di vista delle prestazioni e l'integrità dei dati?

Il mio problema è che voglio tornare un elenco interno dei dati per il chiamante, ma non voglio generare una copia.Thant mi lascia con la restituzione di un riferimento alla lista, o un puntatore alla lista.Tuttavia, io non sono pazzo di lasciare che il chiamante modificare i dati, voglio solo leggere i dati.

  • Devo scegliere tra prestazioni e l'integrità dei dati?
  • Se è così, è, in generale, meglio andare in un modo o è particolare per caso?
  • Ci sono altre alternative?
È stato utile?

Soluzione

RichQ risposta è una ragionevole tecnica, se si utilizza una matrice, vettoriale, etc.

Se si utilizza una collezione che non è indicizzato dai valori ordinali...o pensate di potrebbe essere necessario a un certo punto in un prossimo futuro...allora si potrebbe desiderare di prendere in considerazione di esporre il proprio tipo di iteratore(s), e associati begin()/end() metodi:

class Blah
{
public:
   typedef std::vector<mydata> mydata_collection;
   typedef myDataCollection::const_iterator mydata_const_iterator;

   // ...

   mydata_const_iterator data_begin() const 
      { return myPreciousData.begin(); }
   mydata_const_iterator data_end() const 
      { return myPreciousData.end(); }

private:
   mydata_collection  myPreciousData;
};

...che è possibile utilizzare in modo normale:

Blah blah;
for (Blah::mydata_const_iterator itr = blah.data_begin();
   itr != blah.data_end();
   ++itr)
{
   // ...
}

Altri suggerimenti

Molte volte il chiamante vuole che l'accesso solo a scorrere la collezione.Prendete una pagina di Ruby libro e fare l'iterazione di un aspetto privato della classe.

#include <algorithm>
#include <boost/function.hpp>

class Blah
{
  public:
     void for_each_data(const std::function<void(const mydata&)>& f) const
     {
         std::for_each(myPreciousData.begin(), myPreciousData.end(), f);
     }

  private:
     typedef std::vector<mydata> mydata_collection;
     mydata_collection  myPreciousData;
};

Con questo approccio non stai esponendo nulla circa la vostra interne, cioèche è anche sono una collezione.

Forse qualcosa di simile a questo?

const std::vector<mydata>& getData()
{
  return _myPrivateData;
}

Il vantaggio qui è che è molto, molto semplice, sicuro e si getin C++.È possibile eseguire il cast di questo, come RobQ suggerisce, ma non c'è nulla che tu possa fare per impedire che qualcuno che se non hai la copia.Qui, si dovrebbe utilizzare const_cast, che è abbastanza facile da individuare se stai cercando per esso.

Gli iteratori, in alternativa, potrebbe ottenere è praticamente la stessa cosa, ma è più complicato.L'unico vantaggio di usare gli iteratori qui (che mi vengono in mente) è che si può avere di meglio di incapsulamento.

L'uso di const riferimento o puntatore condiviso sarà utile solo se il contenuto di raccolta sottostante non cambiano nel tempo.

Prendere in considerazione il vostro disegno.Il chiamante ha realmente bisogno di vedere l'interno dell'array?Si può ristrutturare il codice in modo che il chiamante dice oggetto che fare con l'array?E. g., se il chiamante si intende cercare la matrice, il proprietario oggetto di farlo?

Si potrebbe passare un riferimento al vettore risultato per la funzione.Su alcuni compilatori che possono causare marginalmente più veloce del codice.

Vorrei raccomandare cercando di ridisegnare la prima, di andare con una soluzione pulita seconda, per l'ottimizzazione del rendimento terzo (se necessaria).

Uno dei vantaggi di entrambi @Shog9 e @RichQ soluzioni è che essi de-coppia il cliente dalla collezione di attuazione.

Se si decide th cambiare il tipo di raccolta per qualcosa d'altro, i vostri clienti continuano a non funzionare.

Quello che vuoi è l'accesso in sola lettura, senza copiare l'intero blob di dati.Avete un paio di opzioni.

In primo luogo, si può solo restituire un const refererence per qualunque sia il vostro contenitore di dati, come suggerito sopra:

const std::vector<T>& getData() { return mData; }

Questo ha lo svantaggio di concretezza:non si può cambiare come si memorizzano i dati internamente senza modificare l'interfaccia della classe.

In secondo luogo, si può tornare const-ed puntatori ai dati effettivi:

const T* getDataAt(size_t index)
{
   return &mData[index];
}

Questo è un po ' più bello, ma richiede anche che è fornire un getNumItems di chiamata e di protezione contro l'out-of-bounds indici.Inoltre, il const-ness i puntatori è facilmente gettato via, e i vostri dati, è ora di lettura-scrittura.

Un'altra opzione è quella di fornire un paio di iteratori, che è un po ' più complesso.Questo ha gli stessi vantaggi di puntatori, così come non è (necessariamente) la necessità di fornire un'getNumItems chiamata, e non c'è considerevolmente più lavori a striscia la iteratori di loro const-ness.

Probabilmente il modo più semplice per gestire questo è quello di utilizzare un Boost Range:

typedef vector<T>::const_iterator range_iterator_type;
boost::iterator_range< range_iterator_type >& getDataRange()
{
    return boost::iterator_range(mData.begin(), mData.end());
}

Questo ha il vantaggio di intervalli di essere componibile, filtrabile, ecc, come si può vedere sul sito web.

Utilizzando const è una scelta ragionevole.Si potrebbe anche voler controllare il boost C++ library per aver condiviso puntatore attuazione.Esso fornisce i vantaggi di puntatori cioèsi può avere l'obbligo di restituire un puntatore condiviso "null" che un riferimento non consente.

http://www.boost.org/doc/libs/1_36_0/libs/smart_ptr/smart_ptr.htm

Nel tuo caso si dovrebbe fare il puntatore condiviso di tipo const vietare scrive.

Se si dispone di un std::list di pianura vecchio di dati (che cosa .NET chiamerei 'tipi di valore'), per poi tornare un const riferimento a tale elenco andrà bene (ignorando male le cose come const_cast)

Se si dispone di un std::list di puntatori (o boost::shared_ptr's) allora che si fermerà solo si modifica la collezione, non gli elementi in la collezione.Il mio C++ è troppo arrugginito per essere in grado di dirvi la risposta, a questo punto :-(

Io suggerisco di usare il callback lungo le linee di EnumChildWindows.Si dovrà trovare dei mezzi per impedire all'utente di modificare i tuoi dati.Forse usare un const puntatore di riferimento.

D'altra parte, si potrebbe passare una copia di ogni elemento per la funzione di callback di sovrascrivere la copia ogni volta.(Non si desidera generare una copia della vostra intera collezione.Sto solo suggerendo di fare una copia di un elemento alla volta.Che non dovrebbe richiedere molto tempo/memoria).

MyClass tmp;
for(int i = 0; i < n; i++){
    tmp = elements[i];
    callback(tmp);
}

I seguenti due articoli approfondire alcune delle questioni, e la necessità per l'incapsulamento di classi contenitore.Anche se non forniscono una completa lavorato soluzione, essenzialmente portare lo stesso approccio come da Shog9.

Parte 1: Incapsulamento e Vampiri
Parte 2 (registrazione gratuita è ora richiesto di leggere questo): Relitto Treno Spotting
da Kevlin Henney

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