it-swarm.com.de

Ermöglichen Sie die Iteration eines internen Vektors, ohne die Implementierung zu verlieren

Ich habe eine Klasse, die eine Liste von Personen darstellt.

class AddressBook
{
public:
  AddressBook();

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

Ich möchte Kunden erlauben, über den Vektor von Menschen zu iterieren. Der erste Gedanke, den ich hatte, war einfach:

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

Jedoch Ich möchte die Implementierungsdetails nicht an den Client weitergeben. Möglicherweise möchte ich bestimmte Invarianten beibehalten, wenn der Vektor geändert wird, und ich verliere die Kontrolle über diese Invarianten, wenn ich die Implementierung verliere.

Was ist der beste Weg, um eine Iteration zuzulassen, ohne die Interna zu verlieren?

34

Iteration zulassen, ohne die Interna zu verlieren ist genau das, was das Iteratormuster verspricht. Das ist natürlich hauptsächlich Theorie, also hier ein praktisches Beispiel:

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;
};

Sie stellen Standardmethoden begin und end wie Sequenzen in der STL bereit und implementieren sie einfach durch Weiterleiten an die Methode von vector. Dies leckt einige Implementierungsdetails, nämlich dass Sie einen Vektoriterator zurückgeben, aber kein vernünftiger Client sollte jemals davon abhängen, so dass dies kein Problem darstellt. Ich habe hier alle Überladungen gezeigt, aber natürlich können Sie zunächst nur die const-Version angeben, wenn Clients keine People-Einträge ändern können. Die Verwendung der Standardbenennung hat Vorteile: Jeder, der den Code liest, weiß sofort, dass er eine Standarditeration bietet, und funktioniert daher mit allen gängigen Algorithmen, Bereichen, die auf Schleifen basieren usw.

25
stijn

Wenn Iteration alles ist, was Sie brauchen, dann vielleicht ein Wrapper um std::for_each würde genügen:

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;
};
5
catscradle

Sie können das Pimpl-Idiom verwenden und Methoden zum Durchlaufen des Containers bereitstellen.

In der Kopfzeile:

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;
};

In der Quelle:

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

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

Auf diese Weise merkt Ihr Client nicht, welche Art von Container Sie verwenden, wenn er das typedef aus dem Header verwendet. Und die Implementierungsdetails sind vollständig verborgen.

3
BЈовић

Man könnte Mitgliedsfunktionen bereitstellen:

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

Welche den Zugriff ermöglichen, ohne Implementierungsdetails (wie Kontiguität) offenzulegen, und diese innerhalb einer Iteratorklasse verwenden:

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_;
    }
};

Iteratoren können dann vom Adressbuch wie folgt zurückgegeben werden:

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

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

Sie müssten wahrscheinlich die Iterator-Klasse mit Merkmalen usw. ausstatten, aber ich denke, dass dies das tun wird, was Sie gefragt haben.

1
jbcoe

wenn Sie Funktionen von std :: vector genau implementieren möchten, verwenden Sie die folgende private Vererbung und steuern Sie, was verfügbar gemacht wird.

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;
};

Bearbeiten: Dies wird nicht empfohlen, wenn Sie auch die interne Datenstruktur, d. H. Std :: vector, ausblenden möchten

1
Ayub