Perché il vettore <bool> non è un contenitore STL?


99

L'articolo 18 del libro di Scott Meyers STL efficace: 50 modi specifici per migliorare l'uso della libreria di modelli standard dice di evitare vector <bool>in quanto non è un contenitore STL e non contiene realmente bools.

Il codice seguente:

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

non verrà compilato, violando un requisito dei contenitori STL.

Errore:

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

vector<T>::operator []il tipo restituito dovrebbe essere T&, ma perché è un caso speciale per vector<bool>?

In cosa vector<bool>consiste veramente?

L'articolo dice inoltre:

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

Può essere utilizzato in alternativa a vector<bool>?

Qualcuno può spiegare questo?


22
Era un errore di progettazione in C ++ 98, ora mantenuto per compatibilità.
Oktalist

8
@ g-makulik, Non è che l'uso di esso non venga compilato, solo che non puoi memorizzare l'indirizzo di un elemento in un puntatore a bool, poiché l'elemento non ha un proprio indirizzo.
chris


1
@ g-makulik std::vector<bool> v;verrà compilato. &v[0]non (prendendo l'indirizzo di un temporaneo).
Oktalist

4
vector<bool>ha una cattiva reputazione ma non del tutto giustificabile: isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

Risposte:


114

Per motivi di ottimizzazione dello spazio, lo standard C ++ (fin dal C ++ 98) chiama esplicitamente vector<bool>uno speciale contenitore standard in cui ogni bool utilizza solo un bit di spazio anziché un byte come farebbe un normale bool (implementando una sorta di "bitset dinamico"). In cambio di questa ottimizzazione non offre tutte le funzionalità e l'interfaccia di un normale contenitore standard.

In questo caso, poiché non puoi prendere l'indirizzo di un bit all'interno di un byte, cose come operator[]non possono restituire un bool&ma restituiscono invece un oggetto proxy che permette di manipolare il particolare bit in questione. Poiché questo oggetto proxy non è un bool&, non è possibile assegnare il suo indirizzo a un indirizzo bool*come si potrebbe fare con il risultato di una chiamata di operatore di questo tipo su un contenitore "normale". A sua volta questo significa che bool *pb =&v[0];non è un codice valido.

D'altra parte dequenon ha alcuna specializzazione di questo tipo, quindi ogni bool prende un byte e puoi prendere l'indirizzo del valore restituito da operator[].

Infine si noti che l'implementazione della libreria standard di MS è (probabilmente) subottimale in quanto utilizza una piccola dimensione del blocco per deques, il che significa che l'uso di deque come sostituto non è sempre la risposta giusta.


5
abbiamo qualche altro tipo di dati per il quale qualsiasi altro contenitore STL è specializzato o chiamato esplicitamente?
P0W

3
Questo si applica a C ++ 11 std :: array <bool>?
Sergio Basurco

4
@chuckleplant no, std::arrayè semplicemente un wrapper basato su modelli attorno a un array grezzo di T[n]con alcune funzioni di supporto come size()copia / sposta semantica e iteratori aggiunti per renderlo compatibile con STL e (per fortuna) non viola i propri principi a (nota il mio scetticismo su questi :) 'specialize' for ' bool'.
underscore_d

Solo un pignolo: la dimensione di (bool) non è necessariamente un byte. stackoverflow.com/questions/4897844/…
Uri Raz

30

vector<bool>contiene valori booleani in forma compressa utilizzando solo un bit per valore (e non 8 come fanno gli array bool []). Non è possibile restituire un riferimento a un bit in c ++, quindi esiste un tipo di helper speciale, "bit reference", che fornisce un'interfaccia per alcuni bit in memoria e consente di utilizzare operatori e cast standard.


1
@PrashantSrivastava deque<bool>non è specializzato quindi è letteralmente solo un deque che tiene bool.
Konrad Rudolph

@PrashantSrivastava vector<bool>ha un'implementazione del modello specifica. Immagino che altri contenitori STL, come deque<bool>, non lo facciano, quindi contengono bool-s come qualsiasi altro tipo.
Ivan Smirnov

Ecco una domanda che chiede una cosa simile in ruggine dove consentiti bit booleani singoli stackoverflow.com/questions/48875251/...
Andy avvio

25

Il problema è che vector<bool>restituisce un oggetto di riferimento proxy invece di un vero riferimento, in modo che il codice in stile C ++ 98 bool * p = &v[0];non venga compilato. Tuttavia, il moderno C ++ 11 con auto p = &v[0];può essere compilato se restituisceoperator& anche un oggetto puntatore proxy . Howard Hinnant ha scritto un post sul blog che descrive in dettaglio i miglioramenti algoritmici quando si utilizzano tali riferimenti e puntatori proxy.

Scott Meyers ha un lungo Item 30 in C ++ più efficace sulle classi proxy. Puoi fare molta strada per imitare quasi i tipi incorporati: per ogni tipo dato T, una coppia di proxy (ad esempio reference_proxy<T>e iterator_proxy<T>) può essere resa reciprocamente coerente nel senso che reference_proxy<T>::operator&()e iterator_proxy<T>::operator*()sono l'inverso dell'altro.

Tuttavia, a un certo punto è necessario mappare di nuovo gli oggetti proxy per comportarsi come T*o T&. Per i proxy iteratori, è possibile eseguire il sovraccarico operator->()e accedere all'interfaccia del modello Tsenza reimplementare tutte le funzionalità. Tuttavia, per i proxy di riferimento, è necessario eseguire il sovraccarico operator.()e ciò non è consentito nell'attuale C ++ (sebbene Sebastian Redl abbia presentato una proposta del genere su BoostCon 2013). Puoi fare una soluzione dettagliata come un .get()membro all'interno del proxy di riferimento, o implementare tutta Tl'interfaccia di all'interno del riferimento (questo è ciò che viene fatto pervector<bool>::bit_reference), ma questo perderà la sintassi incorporata o introdurrà conversioni definite dall'utente che non hanno la semantica incorporata per le conversioni di tipo (puoi avere al massimo una conversione definita dall'utente per argomento).

TL; DR : no vector<bool>non è un contenitore perché lo Standard richiede un riferimento reale, ma può essere fatto in modo che si comporti quasi come un contenitore, almeno molto più vicino con C ++ 11 (auto) che in C ++ 98.


10

Molti considerano la vector<bool>specializzazione un errore.

In un documento "Deprecare parti di librerie vestigiali in C ++ 17"
c'è una proposta per riconsiderare la specializzazione parziale vettoriale .

C'è stata una lunga storia di specializzazione parziale bool di std :: vector che non soddisfaceva i requisiti del contenitore, e in particolare, i suoi iteratori non soddisfacevano i requisiti di un iteratore ad accesso casuale. Un precedente tentativo di deprecare questo contenitore è stato rifiutato per C ++ 11, N2204 .


Uno dei motivi del rifiuto è che non è chiaro cosa significherebbe deprecare una particolare specializzazione di un modello. Ciò potrebbe essere affrontato con un'attenta formulazione. Il problema più grande è che la specializzazione (impacchettata) del vettore offre un'importante ottimizzazione che i clienti della libreria standard cercano veramente, ma non sarebbero più disponibili. È improbabile che saremo in grado di deprecare questa parte dello standard fino a quando non verrà proposta e accettata una struttura sostitutiva, come N2050 . Sfortunatamente, non ci sono proposte riviste di questo tipo attualmente offerte al Library Evolution Working Group.


5

Guarda come viene implementato. l'STL si basa ampiamente sui modelli e quindi le intestazioni contengono il codice che contengono.

per esempio guarda qui l' implementazione di stdc ++ .

anche interessante anche se non un vettore di bit conforme a stl è llvm :: BitVector da qui .

l'essenza di llvm::BitVectorè una classe annidata chiamata referencee un operatore adatto che sovraccarica per rendere i BitVectorcomportamenti simili vectorcon alcune limitazioni. Il codice seguente è un'interfaccia semplificata per mostrare come BitVector nasconde una classe chiamata referenceper fare in modo che l'implementazione reale si comporti quasi come un vero array di bool senza usare 1 byte per ogni valore.

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

questo codice qui ha le belle proprietà:

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.

Questo codice ha effettivamente un difetto, prova a eseguire:

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

non funzionerà perché assert( (&b[5] - &b[3]) == (5 - 3) );fallirà (entro llvm::BitVector)

questa è la versione molto semplice di llvm. std::vector<bool>contiene anche iteratori funzionanti. quindi la chiamata for(auto i = b.begin(), e = b.end(); i != e; ++i)funzionerà. e anche std::vector<bool>::const_iterator.

Tuttavia ci sono ancora delle limitazioni std::vector<bool>che lo fanno comportare in modo diverso in alcuni casi.


3

Questo proviene da http://www.cplusplus.com/reference/vector/vector-bool/

Vector of bool Questa è una versione specializzata di vector, utilizzata per elementi di tipo bool e ottimizzata per lo spazio.

Si comporta come la versione non specializzata di vector, con le seguenti modifiche:

  • L'archiviazione non è necessariamente una matrice di valori bool, ma l'implementazione della libreria può ottimizzare l'archiviazione in modo che ogni valore venga
    archiviato in un singolo bit.
  • Gli elementi non vengono costruiti utilizzando l'oggetto allocatore, ma il loro valore viene impostato direttamente sul bit appropriato nella memoria interna.
  • Capovolgimento della funzione membro e nuova firma per lo scambio dei membri.
  • Un tipo di membro speciale, riferimento, una classe che accede a singoli bit nella memoria interna del contenitore con un'interfaccia che
    emula un riferimento bool. Al contrario, il tipo di membro const_reference è un semplice bool.
  • I tipi di puntatore e iteratore usati dal contenitore non sono necessariamente né puntatori né iteratori conformi, sebbene
    simulino la maggior parte del loro comportamento previsto.

Queste modifiche forniscono un'interfaccia stravagante a questa specializzazione e favoriscono l'ottimizzazione della memoria rispetto all'elaborazione (che può o meno soddisfare le tue esigenze). In ogni caso, non è possibile istanziare direttamente il template non specializzato di vector per bool. Soluzioni alternative per evitare che questo intervallo utilizzi un tipo diverso (char, unsigned char) o un contenitore (come deque) per utilizzare i tipi di wrapper o specializzarsi ulteriormente per tipi di allocatori specifici.

bitset è una classe che fornisce una funzionalità simile per array di bit di dimensione fissa.


1
Questo non risponde direttamente alla domanda. Nella migliore delle ipotesi, richiede al lettore di dedurre quali cose spiegate in questo riepilogo generale lo rendono non STL.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.