Почему вектор <bool> не является контейнером STL?

В пункте 18 книги Скотта Мейерса "Эффективный STL: 50 конкретных способов улучшить использование стандартной библиотеки шаблонов" говорится, что следует избегать vector <bool>, поскольку он не является контейнером STL и на самом деле он не содержит bool s.

Следующий код:

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

не будет компилироваться, нарушая требования к контейнерам STL.

Ошибка:

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

vector<T>::operator [] тип возвращаемого значения должен быть T&, но почему это особый случай для vector<bool>?

Из чего действительно состоит vector<bool>?

Пункт далее говорит:

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

Можно ли это использовать в качестве альтернативы vector<bool>?

Может кто-нибудь объяснить это?

Ответ 1

Для соображений оптимизации пространства стандарт С++ (еще С++ 98) явно вызывает vector<bool> как специальный стандартный контейнер, в котором каждый bool использует только один бит пространства, а не один байт, как обычный bool (реализация своего рода "динамического бита" ). В обмен на эту оптимизацию он не предлагает все возможности и интерфейс обычного стандартного контейнера.

В этом случае, поскольку вы не можете взять адрес бита в байте, такие вещи, как operator[], не могут вернуть bool&, а вместо этого возвращают прокси-объект, который позволяет манипулировать определенным битом в вопрос. Поскольку этот прокси-объект не является bool&, вы не можете назначить его адрес bool*, как вы могли, в результате такого вызова оператора в "нормальном" контейнере. В свою очередь это означает, что bool *pb =&v[0]; недействительный код.

С другой стороны, deque не имеет такой специализации, так что каждый bool принимает байт, и вы можете взять адрес возвращаемого значения из operator[].

Наконец, обратите внимание, что реализация стандартной библиотеки MS (возможно) субоптимальна в том, что она использует небольшой размер пакета для deques, что означает, что использование deque в качестве замены не всегда является правильным ответом.

Ответ 2

vector<bool> содержит булевские значения в сжатой форме, используя только один бит для значения (а не 8, как это делают массивы bool []). Невозможно вернуть ссылку на бит в С++, поэтому существует специальный вспомогательный тип "бит-ссылка", который предоставляет вам интерфейс с некоторым битом в памяти и позволяет использовать стандартные операторы и приведения.

Ответ 3

Проблема в том, что vector<bool> возвращает ссылочный объект прокси вместо истинной ссылки, так что код стиля C++ 98 bool * p = &v[0]; не будет компилироваться. Однако современный C++ 11 с auto p = &v[0]; может быть скомпилирован, если operator& также возвращает объект указателя прокси. Говард Хиннант написал сообщение в блоге, в котором подробно описываются улучшения алгоритмов при использовании таких ссылок и указателей на прокси.

У Скотта Мейерса есть длинный пункт 30 в Более эффективном C++ о прокси-классах. Вы можете пройти долгий путь, чтобы почти имитировать встроенные типы: для любого данного типа T пара прокси (например, reference_proxy<T> и iterator_proxy<T>) может быть сделана взаимно согласованной в том смысле, что reference_proxy<T>::operator&() и iterator_proxy<T>::operator*() являются обратными.

Однако в какой-то момент нужно сопоставить прокси-объекты обратно, чтобы они вели себя как T* или T&. Для прокси-серверов итераторов можно перегрузить operator->() и получить доступ к интерфейсу шаблона T без переопределения всех функций. Однако для эталонных прокси вам потребуется перегрузить operator.(), что недопустимо в текущем C++ (хотя Себастьян Редл представил такое предложение на BoostCon 2013). Вы можете сделать подробный обходной путь, например, элемент .get() внутри ссылочного прокси-сервера, или реализовать весь интерфейс T внутри ссылки (это то, что сделано для vector<bool>::bit_reference), но это либо приведет к потере встроенного синтаксиса. или ввести пользовательские преобразования, которые не имеют встроенной семантики для преобразования типов (вы можете иметь не более одного пользовательского преобразования на аргумент).

TL; DR: нет vector<bool> не является контейнером, поскольку стандарт требует реальной ссылки, но его можно заставить вести себя почти как контейнер, по крайней мере, намного ближе к C++ 11 (авто), чем в C++ 98.

Ответ 4

Многие считают специализацию vector<bool> ошибкой.

В статье "Устаревшие части рудиментарной библиотеки в С++ 17"
Есть предложение Пересмотреть вектор частичной специализации.

Существует долгая история частичной специализации bool std::vector, не удовлетворяющий требованиям контейнера, и в в частности, его итераторы, не удовлетворяющие требованиям случайного Итератор доступа. Предыдущая попытка отказаться от этого контейнера была отклонено для С++ 11, N2204.


Одна из причин отказа в том, что не ясно, что это будет означает, чтобы отказаться от определенной специализации шаблона. Это может быть решено с осторожной формулировкой. Большая проблема в том, что (упакованный) специализация вектора предлагает важный оптимизация, которую искренне ищут клиенты стандартной библиотеки, но больше не будет доступен. Вряд ли мы сможем не рекомендуется использовать эту часть стандарта до тех пор, пока предложено и принято, например N2050. К сожалению, таких нет пересмотренные предложения, которые в настоящее время предлагаются для Библиотеки Evolution Рабочая группа.

Ответ 5

Посмотрите, как это реализовано. STL основывается на шаблонах, и поэтому заголовки содержат код, который они делают.

например, посмотрите на stdc++ реализацию здесь.

также интересно, хотя не соответствующий stl битовый вектор - это llvm :: BitVector из здесь.

сущность llvm::BitVector - это вложенный класс, называемый reference, и подходящая перегрузка операторов для того, чтобы заставить BitVector вести себя подобно vector с некоторыми ограничениями. Приведенный ниже код представляет собой упрощенный интерфейс, показывающий, как BitVector скрывает класс с именем reference, чтобы реальная реализация работала почти как реальный массив bool без использования 1 байта для каждого значения.

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

этот код обладает хорошими свойствами:

BitVector b(10, false); // size 10, default false
BitVector::reference &x = b[5]; // that 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.

У этого кода есть недостаток, попробуйте запустить:

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

не будет работать, потому что assert( (&b[5] - &b[3]) == (5 - 3) ); потерпит неудачу (в пределах llvm::BitVector)

это очень простая версия llvm. std::vector<bool> также содержит рабочие итераторы. таким образом, вызов for(auto i = b.begin(), e = b.end(); i != e; ++i) будет работать. а также std::vector<bool>::const_iterator.

Однако в std::vector<bool> все еще есть ограничения, которые заставляют его вести себя по-разному в некоторых случаях.

Ответ 6

Это происходит от http://www.cplusplus.com/reference/vector/vector-bool/

Вектор bool Это специализированная версия вектора, которая используется для элементов типа bool и оптимизирует пространство.

Он ведет себя как неспециализированная версия вектора, а следующие изменения:

  • Хранилище не обязательно является массивом значений bool, но реализация библиотеки может оптимизировать хранилище, чтобы каждое значение находилось на уровне хранится в одном бите.
  • Элементы не создаются с использованием объекта-распределителя, но их значение непосредственно устанавливается на правильный бит во внутреннем хранилище.
  • Функция члена flip и новая подпись для обмена членами.
  • Специальный тип элемента, ссылка, класс, который обращается к отдельным битам во внутреннем хранилище контейнера с интерфейсом, который эмулирует ссылку bool. И наоборот, член const_reference является простой bool.
  • Типы указателей и итераторов, используемые контейнером, не обязательно являются ни указателями, ни соответствующими итераторами, хотя они и будут имитировать большую часть ожидаемого поведения.

Эти изменения обеспечивают причудливый интерфейс для этой специализации и оптимизировать оптимизацию памяти для обработки (что может или не подходит твои нужды). В любом случае невозможно создать экземпляр неспециализированный шаблон вектора для bool напрямую. Методы обхода избегайте использования этого диапазона от другого типа (char, без знака char) или контейнер (например, deque) для использования типов оберток или далее специализируются на конкретные типы распределителей.

bitset - это класс, который обеспечивает аналогичную функциональность для фиксированного размера массивы бит.