2009-11-21 5 views
5

OK, also habe ich jetzt zwei (völlig unabhängige, andere Projekt) Klassen mit Iteratoren. One hat iterator und reverse_iterator funktioniert wie beabsichtigt, und die andere, aktuelle hat iterator und eine halb gebrochen const_iterator (insbesondere, weil const_iterator von Iterator abgeleitet ist, ist der Code LinkedList<int>::iterator i = const_list.begin() gültig und ermöglicht es Ihnen, die Const definierten Liste zu ändern ...) .
Ich beabsichtige, alle vier Arten zu dieser Klasse hinzuzufügen ... Wenn ich kann.Wie kann ich das DRY-Prinzip auf Iteratoren in C++ anwenden? (Iterator, Const_iterator, Reverse_iterator, Const_reverse_iterator)

Wie würde ich fortfahren, um den Kopier-/Einfüge-Code zu minimieren und nur den Rückgabetyp zu ändern? Erstellen Sie eine Basisklasse wie base_iterator, von der er erbt? Erstellen Sie eine iterator oder const_iterator und erben Sie davon? Vererben von einigen Std :: Klasse? Wenn einer dieser Fälle der "beste" Ansatz ist, welcher Code geht wo hin?
Vielleicht ist keine der Alternativen gut? Ich bin hier ziemlich verloren und kann nicht viel Referenzmaterial finden.

Jeder Rat ist willkommen, aber bitte bedenken Sie, dass ich neu in diesem Fach bin (sowohl Iteratoren als auch C++ im Allgemeinen, insbesondere OOP). Ich habe vergeblich versucht, die mit GCC gelieferten Header-Dateien zu studieren - sie sind nicht genau das Tutorial, nach dem ich suche.

+0

Stdlib Implementierungen ist in der Regel eines schlechten Wahl zu lernen, für jede Sprache. Sie müssen oft mit externen Schnittstellen (z. B. dem Betriebssystem, anderen Sprachen) umgehen, müssen möglicherweise rückwärtskompatibel mit Code aus dem letzten Jahrzehnt sein, und andere Faktoren, die einfach nicht auf Sie zutreffen. In der Summe: Sie sind nicht mit dem Ziel des Unterrichts geschrieben. Ein gutes Buch ist ein Muss und wird Ihnen viel besser dienen. –

Antwort

3

Manchmal ist die pauschale Anwendung der sogenannten DRY-Regel (Don't Repeat Yourself, für diejenigen, die nicht vertraut sind) nicht der beste Ansatz. Vor allem, wenn Sie neu in der Sprache (C++ und Iteratoren) und OOP selbst (Methodik) sind, gibt es wenig Nutzen bei dem Versuch, die Menge an Code zu minimieren, die Sie gerade schreiben müssen.

Ich würde die beiden Iteratoren mit entsprechenden Code für jeden von ihnen implementieren. Vielleicht, nachdem Sie mehr Erfahrung mit der Sprache, den Werkzeugen und den Techniken haben, gehen Sie zurück und sehen Sie, ob Sie die Menge an Code reduzieren können, indem Sie häufig verwendeten Code ausschließen.

+0

Müssen Sie die Definition von DRY so oft wiederholen, dass Sie es jetzt reflexartig tun? : P –

+0

Wahrscheinlich gute Ratschläge, und ich habe es genommen. Ich habe zwei separate Basisklassen, Iterator und Const_iterator (beide Unterklassen von std :: iterator) und based Reverse_iterator auf Iterator und Const_reverse_iterator auf Reverse_iterator. Die Gesamtzahl der Zeilen für die Iteratoren betrug ~ 70-75. Nicht schlecht für 4 verschiedene Typen. Das schließt die kommende Inline-Dokumentation aus, enthält aber genügend Leerzeichen, um es lesbar zu machen.:) – exscape

+0

Während "zuerst implementieren, später umgestalten" scheint wie ein guter * allgemeiner * Rat, scheint diese Antwort wie ein Cop-Out für die Implementierung von Iteratoren zu sein, wo es wohl bekannt sein sollte, welcher Code oft dupliziert wird. Während "iterator" und "reverse_iterator" möglicherweise erhebliche Unterschiede in der Implementierung aufweisen, sollten "iterator" und "const_iterator" * weitgehend identisch sein (zumindest für jede sinnvolle Implementierung). – jamesdlin

0

LinkedList<int>::iterator i = const_list.begin() Wie sieht Ihre Startmethode aus? Durch die STL Studium können Sie sehen, dass die Behälter definieren zwei solche Methoden mit folgenden Signaturen:

const_iterator begin() const; 
iterator begin(); 

Sie sollten kein Problem haben, einen iterator von einem Objekt qualifiziert als const zu bekommen. Ich denke nicht, dass DRY hier gilt.

+0

Er hat zur Zeit (falsch) const_iterator von iterator abgeleitet, also kann der Code mit den üblichen const/non-const-Überladungen für begin immer noch jeden const_iterator implizit in einen Iterator konvertieren. –

1

Machen Sie den Iterator von const_iterator ableiten, anstatt umgekehrt. Verwenden Sie const_cast entsprechend (als Implementierungsdetail, nicht für Benutzer verfügbar). Dies funktioniert sehr gut in den einfachen Fällen und Modellen, die "Iteratoren const_iterators" direkt sind.

Wenn diese beginnt zu Klärungsbemerkungen in Ihrem Code erfordern, dann schreibe separate Klassen. Sie können lokalisierte Makros verwenden, um für Sie einen ähnlichen Code zu erzeugen, zu vermeiden, dass die Logik zu wiederholen:

struct Container { 
#define G(This) \ 
This& operator++() { ++_internal_member; return *this; } \ 
This operator++(int) { This copy (*this); ++*this; return copy; } 

    struct iterator { 
    G(iterator) 
    }; 
    struct const_iterator { 
    G(const_iterator) 
    const_iterator(iterator); // and other const_iterator specific code 
    }; 
#undef G 
}; 

, dass das Makro scoped ist/lokalisiert ist wichtig, und natürlich ist es nur verwenden, wenn es Ihnen tatsächlich hilft — wenn es führt zu weniger lesbarem Code für Sie, geben Sie es explizit ein.

Und über umgekehrte Iteratoren: Sie können std::reverse_iterator verwenden, um Ihre "normalen" Iteratoren in vielen Fällen zu umhüllen, anstatt sie neu zu schreiben.

struct Container { 
    struct iterator {/*...*/}; 
    struct const_iterator {/*...*/}; 

    typedef std::reverse_iterator<iterator> reverse_iterator; 
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator; 
}; 
0

Einmal habe ich den folgenden Ansatz:

  1. machen eine Template-Klasse common_iterator
  2. typedefs für "Iterator" hinzufügen und "const_iterator"
  3. zu "common_iterator" ein Konstruktor Mitnahmen " Iterator "Typ

Für" Iterator "ersetzt der zusätzliche Konstruktor Standardkopie Konstru In meinem Fall war es äquivalent zum Standard-Kopierkonstruktor.

Für „const_iterator“ es wird ein zusätzlicher Konstruktor sein, die „const_iterator“ von „Iterator“

3

Es ist eigentlich sehr einfach konstruieren kann.

Zunächst einmal, werfen Sie einen Blick auf Boost.Iterator Bibliothek.

Zweitens: Sie müssen eine Basisklasse deklarieren (es ist gut im Beispiel erläutert, wie Sie vorgehen), die dieser ähnlich ist.

template <class Value> 
class BaseIterator: boost::iterator_adaptor<...> {}; 

Sie implementieren die Vorgänge, um den Zeiger dorthin zu bewegen. Beachten Sie, dass Sie, da es sich um eine Anpassung an einen bereits vorhandenen Iterator handelt, diese mit nur wenigen Strichen implementieren können. Es ist wirklich beeindruckend.

Drittens Sie es einfach mit dem const typedef und nicht konstanten Versionen:

typedef BaseIterator<Value> iterator; 
typedef BaseIterator<const Value> const_iterator; 

Die Bibliothek Sie explictly zeigen, wie man die const_iterator Version von der iterator Version konstruierbar sein.

Viertens für die umgekehrte Sache, gibt es ein spezielles reverse_iterator Objekt, das auf einer regelmäßigen Iterator gebaut und rückwärts bewegen :)

Alles in allem eine wirklich elegante und dennoch voll funktionsfähige Weise Iteratoren definieren auf benutzerdefinierte Klassen.

Ich schreibe regelmäßig meine eigenen Container-Adapter, und es geht weniger um DRY als einfach nur ein wenig Tipparbeit zu sparen!

+0

+1 für boost :: iterator_adaptor. Ich benutze es auch und es funktioniert gut für mich. – n1ckp

0

Eine konkretere Version von what maxim1000 suggested:

#include <type_traits> 

template<typename Container, bool forward> 
class iterator_base 
{ 
public: 
    using value_type = 
     typename std::conditional<std::is_const<Container>::value, 
           const typename Container::value_type, 
           typename Container::value_type>::type; 

    iterator_base() { } 

    // For conversions from iterator to const_iterator. 
    template<typename U> 
    iterator_base(const iterator_base<U, forward>& other) 
    : c(other.c) 
    { 
     // .... 
    } 

    value_type& operator*() const 
    { 
     // ... 
    } 

    iterator_base& operator++() 
    { 
     if (forward) 
     { 
      // ... 
     } 
     else 
     { 
      // ... 
     } 
    } 

    iterator_base& operator++(int) 
    { 
     iterator_base copy(*this); 
     ++*this; 
     return copy; 
    } 

private: 
    Container* c = nullptr; 
    // ... 
}; 

using iterator = iterator_base<self_type, true>; 
using const_iterator = iterator_base<const self_type, true>; 

using reverse_iterator = iterator_base<self_type, false>; 
using const_reverse_iterator = iterator_base<const self_type, false>; 
Verwandte Themen