Consentire l'iterazione di un vettore interno senza perdite nell'implementazione

29

Ho una classe che rappresenta un elenco di persone.

class AddressBook
{
public:
  AddressBook();

private:
  std::vector<People> people;
}

Voglio consentire ai clienti di scorrere il vettore delle persone. Il primo pensiero che ho avuto è stato semplicemente:

std::vector<People> & getPeople { return people; }

Tuttavia, non voglio divulgare i dettagli di implementazione al client . Potrei voler mantenere determinati invarianti quando il vettore viene modificato, e perdo l'implementazione, perdo il controllo su questi invarianti.

Qual è il modo migliore per consentire l'iterazione senza perdere le parti interne?

    
posta Elegant Codeworks 06.12.2014 - 06:29
fonte

5 risposte

25

consentire l'iterazione senza perdite di internals è esattamente ciò che promette il pattern iteratore. Ovviamente questa è principalmente teoria, quindi ecco un esempio pratico:

class AddressBook
{
  using peoples_t = std::vector<People>;
public:
  using iterator = peoples_t::iterator;
  using const_iterator = peoples_t::const_iterator;

  AddressBook();

  iterator begin() { return people.begin(); }
  iterator end() { return people.end(); }
  const_iterator begin() const { return people.begin(); }
  const_iterator end() const { return people.end(); }
  const_iterator cbegin() const { return people.cbegin(); }
  const_iterator cend() const { return people.cend(); }

private:
  peoples_t people;
};

Fornisci metodi standard begin e end , proprio come le sequenze nell'STL e li implementano semplicemente inoltrando al metodo di vettore. Questo fa filtrare alcuni dettagli di implementazione e cioè che stai restituendo un iteratore di vettori, ma nessun client sano di mente dovrebbe mai dipendere da questo, quindi non è un problema. Ho mostrato tutti i sovraccarichi qui, ma ovviamente è possibile iniziare semplicemente fornendo la versione const se i client non dovrebbero essere in grado di modificare le voci di People. L'utilizzo della denominazione standard presenta vantaggi: chiunque leggendo il codice sa immediatamente che fornisce l'iterazione "standard" e come tale funziona con tutti gli algoritmi comuni, la gamma basata su cicli ecc.

    
risposta data 06.12.2014 - 10:06
fonte
4

Se l'iterazione è tutto ciò che serve, forse un wrapper attorno a std::for_each sarebbe sufficiente:

class AddressBook
{
public:
  AddressBook();

  template <class F>
  void for_each(F f) const
  {
    std::for_each(begin(people), end(people), f);
  }

private:
  std::vector<People> people;
};
    
risposta data 08.12.2014 - 22:14
fonte
3

Puoi utilizzare l'idioma di Pimpl e fornire metodi per iterare sul contenitore.

Nell'intestazione:

typedef People* PeopleIt;

class AddressBook
{
public:
  AddressBook();


  PeopleIt begin();
  PeopleIt begin() const;
  PeopleIt end();
  PeopleIt end() const;

private:
  struct Imp;
  std::unique_ptr<Imp> pimpl;
};

Nella fonte:

struct AddressBook::Imp
{
  std::vector<People> people;
};

PeopleIt AddressBook::begin()
{
  return &pimpl->people[0];
}

In questo modo, se il tuo cliente usa il typedef dall'intestazione, non noterà che tipo di contenitore stai usando. E i dettagli di implementazione sono completamente nascosti.

    
risposta data 09.12.2014 - 10:25
fonte
1

Uno potrebbe fornire funzioni membro:

size_t Count() const
People& Get(size_t i)

Che consentono l'accesso senza esporre dettagli di implementazione (come contiguità) e utilizzarli all'interno di una classe iteratore:

class Iterator
{
    AddressBook* addressBook_;
    size_t index_;

public:
    Iterator(AddressBook& addressBook, size_t index=0) 
    : addressBook_(&addressBook), index_(index) {}

    People& operator*()
    {
        return addressBook_->Get(index_);
    }

    Iterator& operator ++ ()
    {
       ++index_;
       return *this;
    }

    bool operator != (const Iterator& i) const
    {
        assert(addressBook_ == i.addressBook_);
        return index_ != i.index_;
    }
};

Gli iteratori possono quindi essere restituiti dalla rubrica come segue:

AddressBook::Iterator AddressBook::begin()
{
    return Iterator(this);
}

AddressBook::Iterator AddressBook::end()
{
    return Iterator(this, Count());
}

Probabilmente avresti bisogno di arricchire la classe di iteratori con i tratti ecc., ma penso che ciò farà ciò che hai chiesto.

    
risposta data 08.12.2014 - 23:05
fonte
1

se vuoi l'esatta implementazione delle funzioni da std :: vector, usa l'ereditarietà privata come sotto e controlla ciò che è esposto.

template <typename T>
class myvec : private std::vector<T>
{
public:
    using std::vector<T>::begin;
    using std::vector<T>::end;
    using std::vector<T>::push_back;
};

Modifica:  Questo non è raccomandato se si desidera nascondere anche la struttura interna dei dati, ad es. Std :: vector

    
risposta data 09.12.2014 - 04:54
fonte

Leggi altre domande sui tag