Por que o vetor <bool> não é um contêiner STL?

98

O item 18 do livro de Scott Meyers STL eficaz: 50 maneiras específicas de melhorar seu uso da biblioteca de modelos padrão diz para evitar, vector <bool>pois não é um contêiner STL e realmente não contém bools.

O seguinte código:

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

não irá compilar, violando um requisito de contêineres STL.

Erro:

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

vector<T>::operator []O tipo de retorno deveria ser T&, mas por que é um caso especial vector<bool>?

Em que vector<bool>realmente consiste?

O item diz ainda:

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

Isso pode ser usado como uma alternativa para vector<bool>?

Alguém pode explicar isso?

P0W
fonte
22
Foi um erro de design em C ++ 98, agora mantido para compatibilidade.
Oktalist
8
@ g-makulik, Não é que seu uso não seja compilado, apenas que você não pode armazenar o endereço de um elemento em um ponteiro para bool, já que o elemento não tem seu próprio endereço.
Chris
1
@ g-makulik std::vector<bool> v;irá compilar. &v[0]não (levando endereço de um temporário).
Oktalist
4
vector<bool>tem uma má reputação, mas não totalmente justificável: isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

Respostas:

112

Por motivos de otimização de espaço, o padrão C ++ (já em C ++ 98) chama explicitamente vector<bool>como um contêiner padrão especial onde cada bool usa apenas um bit de espaço em vez de um byte como um bool normal faria (implementando uma espécie de "bitset dinâmico"). Em troca dessa otimização, ele não oferece todos os recursos e interface de um contêiner padrão normal.

Nesse caso, como você não pode obter o endereço de um bit dentro de um byte, coisas como operator[]não podem retornar um, bool&mas em vez disso, retornam um objeto proxy que permite manipular o bit específico em questão. Como esse objeto proxy não é um bool&, você não pode atribuir seu endereço a um bool*como faria com o resultado de uma chamada de operador em um contêiner "normal". Por sua vez, isso significa que esse bool *pb =&v[0];código não é válido.

Por outro lado deque, não há nenhuma especialização chamada, então cada bool leva um byte e você pode pegar o endereço do valor de retorno operator[].

Finalmente, observe que a implementação da biblioteca padrão MS é (discutivelmente) subótima, pois usa um pequeno tamanho de bloco para deques, o que significa que usar deque como substituto nem sempre é a resposta certa.

Mark B
fonte
5
temos algum outro tipo de dados para o qual qualquer outro contêiner STL seja especializado ou explicitamente chamado?
P0W
3
Isso se aplica a C ++ 11 std :: array <bool>?
Sergio Basurco
4
@chuckleplant não, std::arrayé meramente um invólucro modelado em torno de uma matriz bruta de T[n]com algumas funções auxiliares como size()copiar / mover semântica e iteradores adicionados para torná-lo compatível com STL - e (felizmente) não viola seus próprios princípios para (observe meu ceticismo em relação a estes :) 'especializar-se' para ' bool'.
sublinhado_d
Apenas um nit pick - o sizeof (bool) não é necessariamente um byte. stackoverflow.com/questions/4897844/…
Uri Raz
30

vector<bool>contém valores booleanos na forma compactada usando apenas um bit para valor (e não 8 como os arrays bool [] fazem). Não é possível retornar uma referência a um bit em c ++, portanto, há um tipo auxiliar especial, "referência de bit", que fornece uma interface para algum bit na memória e permite o uso de operadores e conversões padrão.

Ivan Smirnov
fonte
1
@PrashantSrivastava deque<bool>não é especializado, então é literalmente apenas um deque segurando bools.
Konrad Rudolph
@PrashantSrivastava vector<bool>tem uma implementação de template específica. Eu acho que outros contêineres STL, como deque<bool>não, então eles contêm bool-s como qualquer outro tipo.
Ivan Smirnov
Aqui está uma pergunta com uma pergunta semelhante na ferrugem, em que eles não permitiam booleans de bit único stackoverflow.com/questions/48875251/…
andy boot
25

O problema é que vector<bool>retorna um objeto de referência de proxy em vez de uma referência verdadeira, de modo que o código de estilo C ++ 98 bool * p = &v[0];não compila. No entanto, o C ++ 11 moderno com auto p = &v[0];pode ser feito para compilar se operator&também retornar um objeto de ponteiro de proxy . Howard Hinnant escreveu uma postagem no blog detalhando as melhorias algorítmicas ao usar tais referências de proxy e ponteiros.

Scott Meyers tem um longo Item 30 em C ++ Mais Eficaz sobre classes de proxy. Você pode percorrer um longo caminho para quase imitar os tipos embutidos: para qualquer tipo T, um par de proxies (por exemplo, reference_proxy<T>e iterator_proxy<T>) pode ser mutuamente consistente no sentido de que reference_proxy<T>::operator&()e iterator_proxy<T>::operator*()são inversos um do outro.

No entanto, em algum ponto, é necessário mapear os objetos proxy de volta para se comportarem como T*ou T&. Para proxies iteradores, pode-se sobrecarregar operator->()e acessar a Tinterface do template sem reimplementar todas as funcionalidades. No entanto, para proxies de referência, você precisaria sobrecarregar operator.(), e isso não é permitido no C ++ atual (embora Sebastian Redl tenha apresentado tal proposta no BoostCon 2013). Você pode fazer uma solução alternativa detalhada como um .get()membro dentro do proxy de referência, ou implementar toda Ta interface de dentro da referência (isto é o que é feito paravector<bool>::bit_reference), mas isso perderá a sintaxe interna ou introduzirá conversões definidas pelo usuário que não têm semântica interna para conversões de tipo (você pode ter no máximo uma conversão definida pelo usuário por argumento).

TL; DR : não vector<bool>não é um contêiner porque o Padrão requer uma referência real, mas pode ser feito para se comportar quase como um contêiner, pelo menos muito mais próximo do C ++ 11 (automático) do que do C ++ 98.

TemplateRex
fonte
10

Muitos consideram a vector<bool>especialização um erro.

Em um artigo "Desprezando partes da biblioteca vestigial em C ++ 17"
há uma proposta para reconsiderar a especialização parcial do vetor .

Há uma longa história de especialização parcial bool de std :: vector que não satisfaz os requisitos do contêiner e, em particular, seus iteradores não satisfazem os requisitos de um iterador de acesso aleatório. Uma tentativa anterior de descontinuar este contêiner foi rejeitada para C ++ 11, N2204 .


Um dos motivos para a rejeição é que não está claro o que significaria descontinuar uma especialização específica de um modelo. Isso poderia ser tratado com uma formulação cuidadosa. O problema maior é que a especialização (compactada) de vetor oferece uma otimização importante que os clientes da biblioteca padrão procuram genuinamente, mas que não estaria mais disponível. É improvável que sejamos capazes de descontinuar esta parte do padrão até que um recurso de substituição seja proposto e aceito, como o N2050 . Infelizmente, não há propostas revisadas sendo oferecidas ao Grupo de Trabalho de Evolução da Biblioteca.

Trevor Hickey
fonte
5

Veja como isso é implementado. a STL se baseia amplamente em modelos e, portanto, os cabeçalhos contêm o código que eles contêm.

por exemplo, veja a implementação stdc ++ aqui .

Também interessante, embora não um stl conformidade vetor de bits é o llvm :: BitVector a partir daqui .

a essência do llvm::BitVectoré uma classe aninhada chamada referencee uma sobrecarga de operador adequada para tornar o BitVectorcomportamento semelhante a, vectorcom algumas limitações. O código a seguir é uma interface simplificada para mostrar como o BitVector oculta uma classe chamada referencepara fazer a implementação real quase se comportar como um array real de bool sem usar 1 byte para cada valor.

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

este código aqui tem as boas propriedades:

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.

Este código realmente tem uma falha, tente executar:

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

não funcionará porque assert( (&b[5] - &b[3]) == (5 - 3) );falhará (dentro llvm::BitVector)

esta é a versão llvm muito simples. std::vector<bool>também tem iteradores funcionais. assim, a chamada for(auto i = b.begin(), e = b.end(); i != e; ++i)funcionará. e também std::vector<bool>::const_iterator.

No entanto, ainda existem limitações std::vector<bool>que o fazem se comportar de forma diferente em alguns casos.

Alex
fonte
3

Isso vem de http://www.cplusplus.com/reference/vector/vector-bool/

Vetor de bool Esta é uma versão especializada de vetor, usada para elementos do tipo bool e otimizada para espaço.

Ele se comporta como a versão não especializada do vetor, com as seguintes alterações:

  • O armazenamento não é necessariamente uma matriz de valores bool, mas a implementação da biblioteca pode otimizar o armazenamento de forma que cada valor seja
    armazenado em um único bit.
  • Os elementos não são construídos usando o objeto alocador, mas seu valor é definido diretamente no bit apropriado no armazenamento interno.
  • Inverter função de membro e uma nova assinatura para troca de membro.
  • Um tipo de membro especial, referência, uma classe que acessa bits individuais no armazenamento interno do contêiner com uma interface que
    emula uma referência de bool. Por outro lado, o tipo de membro const_reference é um bool simples.
  • Os tipos de ponteiro e iterador usados ​​pelo contêiner não são necessariamente nem ponteiros nem iteradores em conformidade, embora eles
    devam simular a maior parte de seu comportamento esperado.

Essas mudanças fornecem uma interface peculiar para essa especialização e favorecem a otimização da memória em relação ao processamento (que pode ou não atender às suas necessidades). Em qualquer caso, não é possível instanciar o modelo não especializado de vetor para bool diretamente. Soluções alternativas para evitar que esse intervalo use um tipo diferente (char, unsigned char) ou contêiner (como deque) para usar tipos de invólucro ou se especializar ainda mais em tipos de alocadores específicos.

bitset é uma classe que fornece uma funcionalidade semelhante para matrizes de bits de tamanho fixo.

kvv
fonte
1
Isso não responde à pergunta diretamente. Na melhor das hipóteses, requer que o leitor inferir quais coisas explicadas neste resumo geral o tornam não STL.
sublinhado_d