Warum ist vector <bool> kein STL-Container?

98

Punkt 18 von Scott Meyers 'Buch Effective STL: 50 Spezifische Möglichkeiten zur Verbesserung Ihrer Verwendung der Standardvorlagenbibliothek sollten vermieden werden , vector <bool>da es sich nicht um einen STL-Container handelt und nicht wirklich bools enthält.

Der folgende Code:

vector <bool> v; 
bool *pb =&v[0];

wird nicht kompiliert, was eine Anforderung von STL-Containern verletzt.

Error:

cannot convert 'std::vector<bool>::reference* {aka std::_Bit_reference*}' to 'bool*' in initialization

vector<T>::operator []Rückgabetyp soll sein T&, aber warum ist es ein Sonderfall für vector<bool>?

Woraus besteht vector<bool>eigentlich?

Der Artikel sagt weiter:

deque<bool> v; // is a STL container and it really contains bools

Kann dies als Alternative zu verwendet werden vector<bool>?

Kann das bitte jemand erklären?

P0W
quelle
22
Es war ein Designfehler in C ++ 98, der jetzt aus Kompatibilitätsgründen beibehalten wurde.
Oktalist
8
@ g-makulik, Es ist nicht so, dass die Verwendung nicht kompiliert werden kann, nur dass Sie die Adresse eines Elements nicht in einem Zeiger auf speichern können bool, da das Element keine eigene Adresse hat.
Chris
1
@ g-makulik std::vector<bool> v;wird kompiliert. &v[0]wird nicht (Adresse eines temporären nehmen).
Oktalist
4
vector<bool>hat einen schlechten Ruf, aber nicht ganz zu Recht: isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

Antworten:

112

Aus Gründen der Speicherplatzoptimierung wird der C ++ - Standard (bereits in C ++ 98) explizit vector<bool>als spezieller Standardcontainer aufgerufen, in dem jeder Bool nur ein Bit Speicherplatz anstelle eines Bytes wie ein normaler Bool verwendet (Implementierung einer Art von "dynamisches Bitset"). Im Gegenzug für diese Optimierung bietet es nicht alle Funktionen und Schnittstellen eines normalen Standardcontainers.

In diesem Fall können Dinge wie a operator[]nicht zurückgeben, bool&sondern ein Proxy-Objekt zurückgeben, mit dem das betreffende Bit bearbeitet werden kann, da Sie die Adresse eines Bits nicht innerhalb eines Bytes übernehmen können . Da es sich bei diesem Proxy-Objekt nicht um ein handelt bool&, können Sie seine Adresse nicht einem bool*ähnlichen Objekt zuweisen, wie dies bei einem solchen Operatoraufruf für einen "normalen" Container der Fall wäre. Dies bedeutet wiederum, dass der bool *pb =&v[0];Code nicht gültig ist.

Auf der anderen Seite dequewird keine solche Spezialisierung aufgerufen, sodass jeder Bool ein Byte benötigt und Sie die Adresse des Wertes verwenden können, von dem zurückgegeben wird operator[].

Beachten Sie schließlich, dass die Implementierung der MS-Standardbibliothek (wohl) suboptimal ist, da sie eine kleine Blockgröße für Deques verwendet, was bedeutet, dass die Verwendung von Deque als Ersatz nicht immer die richtige Antwort ist.

Mark B.
quelle
5
Haben wir einen anderen Datentyp, für den ein anderer STL-Container spezialisiert ist oder explizit aufgerufen wird?
P0W
3
Gilt dies für C ++ 11 std :: array <bool>?
Sergio Basurco
4
@chuckleplant no, std::arrayist lediglich ein Vorlagen-Wrapper um ein rohes Array T[n]mit einigen Hilfsfunktionen wie size()Kopier- / Verschiebungssemantik und Iteratoren, die hinzugefügt wurden, um es STL-kompatibel zu machen - und (zum Glück) verstößt es nicht gegen seine eigenen Prinzipien (beachten Sie meine Skepsis gegenüber diesen :) 'spezialisieren' für ' bool'.
underscore_d
Nur eine Kleinigkeit - die Größe von (bool) ist nicht unbedingt ein Byte. stackoverflow.com/questions/4897844/…
Uri Raz
30

vector<bool>enthält boolesche Werte in komprimierter Form, wobei nur ein Bit für den Wert verwendet wird (und nicht 8, wie es bool [] -Arrays tun). Es ist nicht möglich, einen Verweis auf ein Bit in c ++ zurückzugeben, daher gibt es einen speziellen Hilfstyp, "Bitreferenz", der Ihnen eine Schnittstelle zu einem Bit im Speicher bietet und die Verwendung von Standardoperatoren und Casts ermöglicht.

Ivan Smirnov
quelle
1
@PrashantSrivastava deque<bool>ist nicht spezialisiert, es ist also buchstäblich nur eine Deque, die Bools hält.
Konrad Rudolph
@PrashantSrivastava vector<bool>verfügt über eine bestimmte Vorlagenimplementierung. Ich denke, andere STL-Container, wie z. B. deque<bool>nicht, enthalten Bool-s wie alle anderen Typen.
Ivan Smirnov
Hier ist eine Frage, die eine ähnliche Sache in Rost stellt, wo sie einzelne Bit-Boolesche Werte
boot
25

Das Problem besteht darin, dass anstelle einer echten Referenz vector<bool>ein Proxy-Referenzobjekt zurückgegeben wird , sodass der Code bool * p = &v[0];im C ++ 98-Stil nicht kompiliert wird. Modernes C ++ 11 mit auto p = &v[0];kann jedoch kompiliert werden, wenn operator&auch ein Proxy-Zeigerobjekt zurückgegeben wird . Howard Hinnant hat einen Blog-Beitrag geschrieben, in dem die algorithmischen Verbesserungen bei der Verwendung solcher Proxy-Referenzen und Zeiger beschrieben werden.

Scott Meyers hat einen langen Artikel 30 in Effective C ++ über Proxy-Klassen. Sie können einen langen Weg zurücklegen, um die eingebauten Typen fast nachzuahmen: Für jeden gegebenen Typ Tkann ein Paar von Proxys (z. B. reference_proxy<T>und iterator_proxy<T>) in dem Sinne miteinander konsistent gemacht werden, dass reference_proxy<T>::operator&()und iterator_proxy<T>::operator*()umgekehrt sind.

Irgendwann muss man jedoch die Proxy-Objekte wieder zuordnen, um sich wie T*oder zu verhalten T&. Bei Iterator-Proxys kann man die Benutzeroberfläche operator->()der Vorlage überladen und darauf zugreifen T, ohne die gesamte Funktionalität neu zu implementieren. Für Referenz-Proxys müssten Sie jedoch überladen operator.(), was in aktuellem C ++ nicht zulässig ist (obwohl Sebastian Redl einen solchen Vorschlag auf der BoostCon 2013 vorgelegt hat). Sie können eine ausführliche Problemumgehung wie ein .get()Mitglied im Referenz-Proxy durchführen oder die gesamte TSchnittstelle in der Referenz implementieren (hierfür wird gearbeitet)vector<bool>::bit_reference), aber dies verliert entweder die eingebaute Syntax oder führt benutzerdefinierte Konvertierungen ein, die keine integrierte Semantik für Typkonvertierungen haben (Sie können höchstens eine benutzerdefinierte Konvertierung pro Argument haben).

TL; DR : no vector<bool>ist kein Container, da der Standard eine echte Referenz erfordert, aber es kann bewirkt werden, dass es sich fast wie ein Container verhält, zumindest viel näher mit C ++ 11 (auto) als mit C ++ 98.

TemplateRex
quelle
10

Viele halten die vector<bool>Spezialisierung für einen Fehler.

In einem Artikel "Verwerfen von Restbibliotheksteilen in C ++ 17"
wird vorgeschlagen, die Teilspezialisierung von Vektoren zu überdenken .

Es gibt eine lange Geschichte der bool-Teilspezialisierung von std :: vector, die die Containeranforderungen nicht erfüllt, und insbesondere der Iteratoren, die die Anforderungen eines Direktzugriffsiterators nicht erfüllen. Ein früherer Versuch, diesen Container zu verwerfen, wurde für C ++ 11, N2204 abgelehnt .


Einer der Gründe für die Ablehnung ist, dass nicht klar ist, was es bedeuten würde, eine bestimmte Spezialisierung einer Vorlage abzulehnen. Das könnte mit sorgfältiger Formulierung angegangen werden. Das größere Problem ist, dass die (gepackte) Spezialisierung von Vektoren eine wichtige Optimierung bietet, die Clients der Standardbibliothek wirklich suchen, aber nicht mehr verfügbar wären. Es ist unwahrscheinlich, dass wir diesen Teil des Standards verwerfen können, bis eine Ersatzanlage wie N2050 vorgeschlagen und akzeptiert wird . Leider werden der Library Evolution Working Group derzeit keine derartigen überarbeiteten Vorschläge angeboten.

Trevor Hickey
quelle
5

Schauen Sie sich an, wie es implementiert wird. Die STL baut weitgehend auf Vorlagen auf, und daher enthalten die Header den Code, den sie enthalten.

Schauen Sie sich hier zum Beispiel die stdc ++ - Implementierung an .

auch interessant, obwohl kein stl- konformer bitvektor der llvm :: bitVector von hier ist .

Die Essenz von llvm::BitVectorist eine verschachtelte Klasse namens referenceund eine geeignete Überladung von Operatoren, um das BitVectorVerhalten vectormit einigen Einschränkungen ähnlich zu machen . Der folgende Code ist eine vereinfachte Schnittstelle, die zeigt, wie BitVector eine aufgerufene Klasse verbirgt reference, damit sich die reale Implementierung fast wie ein echtes Array von Bool verhält, ohne 1 Byte für jeden Wert zu verwenden.

class BitVector {
public:
  class reference {
    reference &operator=(reference t);
    reference& operator=(bool t);
    operator bool() const;
  };
  reference operator[](unsigned Idx);
  bool operator[](unsigned Idx) const;      
};

Dieser Code hier hat die schönen Eigenschaften:

BitVector b(10, false); // size 10, default false
BitVector::reference &x = b[5]; // that's what really happens
bool y = b[5]; // implicitly converted to bool 
assert(b[5] == false); // converted to bool
assert(b[6] == b[7]); // bool operator==(const reference &, const reference &);
b[5] = true; // assignment on reference
assert(b[5] == true); // and actually it does work.

Dieser Code hat tatsächlich einen Fehler. Versuchen Sie Folgendes auszuführen:

std::for_each(&b[5], &b[6], some_func); // address of reference not an iterator

wird nicht funktionieren, weil assert( (&b[5] - &b[3]) == (5 - 3) );(innerhalb llvm::BitVector) fehlschlagen wird

Dies ist die sehr einfache llvm-Version. std::vector<bool>hat auch funktionierende Iteratoren drin. Somit for(auto i = b.begin(), e = b.end(); i != e; ++i)funktioniert der Anruf . und auch std::vector<bool>::const_iterator.

Es gibt jedoch immer noch Einschränkungen std::vector<bool>, die dazu führen, dass es sich in einigen Fällen anders verhält .

Alex
quelle
3

Dies kommt von http://www.cplusplus.com/reference/vector/vector-bool/

Vektor von Bool Dies ist eine spezielle Version von Vektor, die für Elemente vom Typ Bool verwendet und für den Raum optimiert wird.

Es verhält sich wie die nicht spezialisierte Version des Vektors mit den folgenden Änderungen:

  • Der Speicher ist nicht unbedingt ein Array von Bool-Werten, aber die Bibliotheksimplementierung kann den Speicher so optimieren, dass jeder Wert
    in einem einzelnen Bit gespeichert wird.
  • Elemente werden nicht mit dem Allokatorobjekt erstellt, sondern ihr Wert wird direkt auf das richtige Bit im internen Speicher gesetzt.
  • Mitgliederfunktionswechsel und eine neue Signatur für den Mitgliedertausch.
  • Ein spezieller Elementtyp, Referenz, eine Klasse, die über eine Schnittstelle,
    die eine Bool-Referenz emuliert , auf einzelne Bits im internen Speicher des Containers zugreift . Umgekehrt ist der Elementtyp const_reference ein einfacher Bool.
  • Die vom Container verwendeten Zeiger- und Iteratortypen sind nicht unbedingt weder Zeiger noch konforme Iteratoren, obwohl sie
    den größten Teil ihres erwarteten Verhaltens simulieren sollen.

Diese Änderungen bieten eine eigenartige Schnittstelle zu dieser Spezialisierung und bevorzugen die Speicheroptimierung gegenüber der Verarbeitung (die möglicherweise Ihren Anforderungen entspricht oder nicht). In jedem Fall ist es nicht möglich, die nicht spezialisierte Vorlage des Vektors für bool direkt zu instanziieren. Umgehungen, um diesen Bereich zu vermeiden, verwenden Sie keinen anderen Typ (char, unsigned char) oder Container (wie deque), um Wrapper-Typen zu verwenden, oder spezialisieren Sie sich weiter auf bestimmte Allokatortypen.

Bitset ist eine Klasse, die eine ähnliche Funktionalität für Bit-Arrays mit fester Größe bietet.

kvv
quelle
1
Dies beantwortet die Frage nicht direkt. Bestenfalls muss der Leser ableiten, welche in dieser allgemeinen Zusammenfassung erläuterten Dinge es zu einer Nicht-STL machen.
underscore_d