...
-
Ok, hier die (viel) bessere Version: http://ideone.com/2jXHS8
Hätte nicht so stur sein sollen. Jetzt gibt es auch eineconst_iterator-Klasse.
Außerdem ist jetzt mStart der Knoten vor dem ersten Element, was auch einige unnötige Abfragen und drumherum-Blödsinn vermeidet.
Und natürlich kein Zeiger mehr in Node.
Das Erzeugen von Nodes ist intemplate<typename... Args> Node* _alloc_make_node( NodeBase* prev, NodeBase* next, Args&&... args ) { Node* node = allocator_traits::allocate( mAllocator, 1 ); try { allocator_traits::construct( mAllocator, node, prev, next, std::forward<Args>(args)... ); } catch(...) { allocator_traits::deallocate( mAllocator, node, 1 ); throw; } return node; }ausgelagert. Nodes mit Elementen sind
Node-NodeBaseist die Basisklasse, in der keine Daten enthalten sind, sondern nur der Zeiger auf die nächste oder vorigeNodeBase.mStartundmEndsindNodeBasesEdit:
@const_iterator: Mach iterator als Template über value_type, dann kann der einmal const und einmal non-const sein.
Ja, so geht es schöner.
-
Sone schrieb:
template<typename... Args> Node* _alloc_make_node( NodeBase* prev, NodeBase* next, Args&&... args ) { Node* node = allocator_traits::allocate( mAllocator, 1 ); try { allocator_traits::construct( mAllocator, node, prev, next, std::forward<Args>(args)... ); } catch(...) { allocator_traits::deallocate( mAllocator, node, 1 ); throw; } return node; }=>
template<typename... Args> Node* _alloc_make_node( NodeBase* prev, NodeBase* next, Args&&... args ) { auto dealloc = [&](){allocator_traits::deallocate( mAllocator, node, 1 );}; std::unique_ptr<Node, decltype(dealloc)> node(allocator_traits::allocate( mAllocator, 1 ), dealloc); allocator_traits::construct( mAllocator, node, prev, next, std::forward<Args>(args)... ); return node.release(); }
-
Sone schrieb:
if( prev ) prev->next = node; if( next ) next->prev = node;Mein Code hat da keine ifs.
-
haifaik schrieb:
Sone schrieb:
if( prev ) prev->next = node; if( next ) next->prev = node;Mein Code hat da keine ifs.
Meiner auch nicht. Wo hast du das her?
Edit: Ah, ich sehe. Bei der _notify-Version. Gut, das wahr tatsächlich absichtlich - aber nicht nötig.
-
struct const_iterator : std::iterator<std::bidirectional_iterator_tag, value_type const, difference_type, const_pointer, const_reference>Das soll sicher value_type ohne const sein.
Der intern verwendete Allocator ist nicht Teil des Interfaces. Es müssen also zwei Allocatorobjekte gespeichert werden (sinnvollerweise nutzt man hier EBO aus, wenn möglich).
-
camper schrieb:
Der intern verwendete Allocator ist nicht Teil des Interfaces. Es müssen also zwei Allocatorobjekte gespeichert werden (sinnvollerweise nutzt man hier EBO aus, wenn möglich).
Wieso zwei? Man braucht doch nur den internen
allocator<node<T>>, derallocator<T>ist unnütz.Sone schrieb:
struct NodeBase {...} mStart, mEnd; mutable NodeBase mStart, mEnd;Da ist zu viel Code, als dass ich Lust habe durchzugucken, aber mir wird nicht klar, weshalb du 4 Member brauchst bzw. 2 Member möchtest. mStart ist irgendwie sinnlos.
-
haifaik schrieb:
camper schrieb:
Der intern verwendete Allocator ist nicht Teil des Interfaces. Es müssen also zwei Allocatorobjekte gespeichert werden (sinnvollerweise nutzt man hier EBO aus, wenn möglich).
Wieso zwei? Man braucht doch nur den internen
allocator<node<T>>, derallocator<T>ist unnütz.Stimmt, aber get_allocator muss eine Kopie zurückgeben.
-
Sone schrieb:
struct NodeBase {...} mStart, mEnd; mutable NodeBase mStart, mEnd;Da ist zu viel Code, als dass ich Lust habe durchzugucken, aber mir wird nicht klar, weshalb du 4 Member brauchst bzw. 2 Member möchtest. mStart ist irgendwie sinnlos.
Nein, nein. Das war wieder ein Flüchtigkeitsfehler.
mStartundmEndin der abgeleiteten Klasse fliegen raus.
Edit: Ja, gefixt: http://ideone.com/2jXHS8
-
camper schrieb:
haifaik schrieb:
camper schrieb:
Der intern verwendete Allocator ist nicht Teil des Interfaces. Es müssen also zwei Allocatorobjekte gespeichert werden (sinnvollerweise nutzt man hier EBO aus, wenn möglich).
Wieso zwei? Man braucht doch nur den internen
allocator<node<T>>, derallocator<T>ist unnütz.Stimmt, aber get_allocator muss eine Kopie zurückgeben.
Das dachte ich auch gerade. So gibt man ja AFAICS den Zugriff auf
Nodefrei... das geht ja nicht. Dann muss also ein unbenutzter Allokator gehalten und zurückgegeben werden?
-
camper schrieb:
Stimmt, aber get_allocator muss eine Kopie zurückgeben.
Schlüsselwort "Kopie". Zum Glück ist es ein requirement, dass es einen entsprechenden Kopierkonstruktor vom rebindeten Allocator gibt.
-
Sone schrieb:
mStart ist irgendwie sinnlos.
Edit: Ja, gefixt: http://ideone.com/2jXHS8
mStart ist immer noch unnötig.
-
haifaik schrieb:
Sone schrieb:
mStart ist irgendwie sinnlos.
Edit: Ja, gefixt: http://ideone.com/2jXHS8
mStart ist immer noch unnötig.
Willst du einen Zeiger auf die NodeBase vor dem ersten Element als
next-Zeiger inmEndspeichern? Der kleine "Zirkel"-Trick?
-
Sone schrieb:
Willst du einen Zeiger auf die NodeBase vor dem ersten Element als
next-Zeiger inmEndspeichern? Der kleine "Zirkel"-Trick?Exakt.
-
haifaik schrieb:
camper schrieb:
Stimmt, aber get_allocator muss eine Kopie zurückgeben.
Schlüsselwort "Kopie". Zum Glück ist es ein requirement, dass es einen entsprechenden Kopierkonstruktor vom rebindeten Allocator gibt.
Wohl eher "Pseudo-Kopie"
Der Standard verwendet in diesem Absatz 23.2.1/7 einen ziemlich weitgefassten Kopie-Begriff. Normalerweise ist eine Kopie eines Objektes ja ein Objekt des gleichen Typs wie des Ausgangsobjektes.So wie es gemeint ist, macht es nat. auch viel mehr Sinn, danke für den Hinweis.
@Sone: wieso mutable?
-
#define DEFINE_OP( class, mem, ch ) \ class& operator ch() noexcept \ { \ p = p->mem; \ return *this; \ } #define DEFINE_POST_OP( class, ch ) \ class operator ch(int) noexcept \ { \ auto rval = *this; \ ch*this; \ return rval; \ } private: template<typename cv_value_t> struct iterator_base : std::iterator<std::bidirectional_iterator_tag, cv_value_t, difference_type> { private: using base = std::iterator<std::bidirectional_iterator_tag, cv_value_t, difference_type>; friend class List; NodeBase* p; iterator_base( NodeBase* p ) noexcept : p{p} {} public: iterator_base() noexcept : iterator_base{nullptr} {} iterator_base( iterator_base<typename std::remove_cv<cv_value_t>::type> const& it ) noexcept: p{it.p} {} typename base::reference operator*() noexcept { return static_cast<Node*>(p)->data; } typename base::pointer operator->() noexcept { return &static_cast<Node*>(p)->data; } DEFINE_OP( iterator_base, next, ++ ) DEFINE_OP( iterator_base, prev, -- ) DEFINE_POST_OP( iterator_base, ++ ) DEFINE_POST_OP( iterator_base, -- ) /// Vergleichs-Operatoren als Member: der operator!= hat bei der globalen Version irgendwie nicht funktioniert. Macht hier sowieso keinen Unterschied. template<typename cv_value_t_other> bool operator==( iterator_base<cv_value_t_other> other ) const noexcept { return p == other.p; } template<typename cv_value_t_other> bool operator!=( iterator_base<cv_value_t_other> other ) const noexcept { return !(*this == other); } }; #undef DEFINE_POST_OP #undef DEFINE_OP public: typedef iterator_base<value_type> iterator; typedef iterator_base<value_type const> const_iterator;(Funktioniert)
?camper schrieb:
@Sone: wieso mutable?
Mach mal ohne. Wenn das List-Objekt selbst const ist, dann wird bei einer Rückgabe in bspw.
begin() &mStartja zu einemconst_iteratorkonvertiert.const_iteratorakzeptiert aber im Konvertierungskonstruktor nur Zeiger auf non-const NodeBases. Wenn ich aber den Pointee-Typ in iterator_base mit const qualifiziere, dann führt das dazu, dass in erase und anderen Funktionen, die einenconst_iteratorannehmen, ...iterator erase( const_iterator it ) { auto rval = it.p->next; it.p->prev->next = it.p->next; it.p->next->prev = it.p->prev; _alloc_destroy_node( static_cast<Node*>(it.p) ); // ... hier ein Fehler auftritt. Dann muss da ein const_cast rein, denn die _alloc-Funktionen nehmen ja keine solchen Zeiger. Und da ist mir persönlich mutable lieber. --mSize; return rval; }
-
Der const_cast ist rein logisch schon korrekt und damit auch nicht boese.
-
template<typename cv_value_t> struct iterator_base : std::iterator<std::bidirectional_iterator_tag, cv_value_t, difference_type> { private: using base = std::iterator<std::bidirectional_iterator_tag, cv_value_t, difference_type>;=>
template<typename cv_value_t, typename Base=std::iterator<std::bidirectional_iterator_tag, cv_value_t, difference_type> > struct iterator_base : Base {
-
DAU anfällig.
-
Es geht auch ohne cast: mStart.next->prev

-
camper schrieb:
Es geht auch ohne cast: mStart.next->prev

Ist mStart const-qualifiziert, ist auch der Member-Zeiger
const-qualifiziert. Dann brauche ich einenconst_cast, um den Zeiger non-const zu machen (anstatt den Pointee).
Von welchem Cast redest du eigentlich?Kellerautomat schrieb:
Der const_cast ist rein logisch schon korrekt und damit auch nicht boese.
Es geht mir nicht darum, ob er Böse" ist, sondern darum, dass ich das Problem lieber an der Wurzel packe.
mStartundmEndgehören nicht zur Konstanz der Instanz dazu - wenn die Instanz alsconstqualifiziert ist, dann betrifft dass alle ihre **Listenelemente, aber nicht die HilfsknotenmStartundmEnd**.