Angenommen, ich habe eine C ++ - Klasse, Container
, die einige Elemente vom Typ Element
enthält. Aus verschiedenen Gründen ist es ineffizient, unerwünscht, unnötig, unpraktisch und / oder unmöglich (1), den Inhalt nach der Konstruktion zu modifizieren oder zu ersetzen. Etwas in der Art von const std::list<const Element>
(2).
Container
kann viele Anforderungen der STL-Konzepte "Container" und "Sequenz" erfüllen. Es kann die verschiedenen Typen wie value_type
, reference
usw. bereitstellen. Es kann einen Standardkonstruktor, einen Kopierkonstruktor, einen const_iterator
Typ, begin() const
, end() const
, size
, empty
, alle Vergleichsoperatoren und möglicherweise einige von rbegin() const
, rend() const
, front()
, back()
, operator[]()
und at()
.
Jedoch kann Container
nicht liefern insert
, erase
, clear
, push_front
, push_back
, nicht-konstant front
, nicht-konstant back
, nicht-konstant operator[]
oder nicht-const at
mit der erwarteten Semantik. So scheint es, dass Container
sich nicht als "Sequenz" qualifizieren kann. Ferner kann Container
nicht operator=
und swap
bereitstellen und kann keinen iterator
-Typ bereitstellen, der auf ein nicht-konstantes Element zeigt. Es kann also nicht einmal als "Container" bezeichnet werden.
Gibt es ein weniger fähiges STL-Konzept, das Container
erfüllt? Gibt es einen "Nur-Lese-Container" oder einen "unveränderlichen Container"?
Wenn Container
kein definiertes Konformitätsniveau erfüllt, gibt es einen Wert in Teilkonformität? Ist es irreführend, dass es wie ein "Container" aussieht, wenn es sich nicht qualifiziert? Gibt es eine prägnante, eindeutige Möglichkeit, die Konformität zu dokumentieren, damit ich die konforme Semantik nicht explizit dokumentieren muss? Und ähnlich, eine Möglichkeit, es zu dokumentieren, so dass zukünftige Benutzer wissen, dass sie den Read-only-generischen Code nutzen können, aber nicht erwarten, dass mutierende Algorithmen funktionieren.
Was bekomme ich, wenn ich das Problem lockere, so dass Container
Assignable ist (aber seine Elemente nicht sind)? An diesem Punkt sind operator=
und swap
möglich, aber Dereferenzierung von iterator
gibt immer noch const Element
zurück. Ist Container
jetzt ein "Container"?
const std::list<T>
hat ungefähr dieselbe Schnittstelle wie Container
. Bedeutet das, dass es weder ein "Container" noch eine "Sequenz" ist?
Fußnote (1) Ich habe Anwendungsfälle, die dieses ganze Spektrum abdecken. Ich habe eine Möchtegern-Container-Klasse, die einige schreibgeschützte Daten anpasst, also muss sie unveränderlich sein. Ich habe einen Möchtegern-Container, der bei Bedarf eigene Inhalte generiert, also veränderbar ist, aber Sie können Elemente nicht so ersetzen, wie es die STL erfordert. Ich habe noch einen anderen Möchtegern-Container, der seine Elemente so speichert, dass insert()
so langsam wird, dass es niemals nützlich wäre. Und schließlich habe ich eine Zeichenfolge, die Text in UTF-8 speichert, während eine Codepunkt-orientierte Schnittstelle freigelegt wird; eine veränderbare Implementierung ist möglich, aber völlig unnötig.
Fußnote (2) Dies ist nur zur Veranschaulichung. Ich bin mir ziemlich sicher, dass std::list
einen zuweisbaren Elementtyp benötigt.
Die STL definiert keine niedrigeren Konzepte; vor allem, weil die Idee von const
normalerweise auf einer Per-Iterator- oder Per-Reference-Ebene ausgedrückt wird, nicht auf einer Pro-Klassen-Ebene.
Sie sollten iterator
nicht mit unerwarteten Semantiken versehen, sondern nur const_iterator
. Dies ermöglicht Client-Code an der logischsten Stelle (mit der am besten lesbaren Fehlermeldung) zu versagen, wenn sie einen Fehler machen.
Möglicherweise ist der einfachste Weg das zu tun, es zu kapseln und alle nicht-const-Aliase zu verhindern.
%Vor%Nun weiß jeder Client-Code genau, was er mit dem Rückgabewert von Resultsnada tun kann, der eine Mutation erfordert.
Solange Ihr Objekt einen Const_iterator bereitstellen kann, muss es nichts anderes haben. Es sollte ziemlich einfach sein, dies in Ihrer Containerklasse zu implementieren.
(Sehen Sie sich ggf. die Bibliothek Boost.Iterators an; sie enthält die Klassen iterator_facade und iterator_adaptor, die Sie bei den Details unterstützen)