Почему vector ‹bool› не является контейнером STL?

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

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

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>?

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


person P0W    schedule 22.07.2013    source источник
comment
Это была ошибка дизайна C ++ 98, которая теперь сохранена для совместимости.   -  person Oktalist    schedule 22.07.2013
comment
Итак, мне также интересно, почему std::vector<bool> не должен компилироваться. Еще помню, что это специализация.   -  person πάντα ῥεῖ    schedule 22.07.2013
comment
@ g-makulik, дело не в том, что его использование не компилируется, просто вы не можете сохранить адрес элемента в указателе на bool, так как у элемента нет своего адреса.   -  person chris    schedule 22.07.2013
comment
@chris А, понятно! Да, ты не можешь этого сделать.   -  person πάντα ῥεῖ    schedule 22.07.2013
comment
@ g-makulik std::vector<bool> v; скомпилирует. &v[0] не будет (взяв адрес временный).   -  person Oktalist    schedule 22.07.2013
comment
@Oktalist Тогда что с deque ‹bool›?   -  person P0W    schedule 22.07.2013
comment
@PrashantSrivastava Он не специализируется на bool и поэтому ведет себя разумно.   -  person Bartek Banachewicz    schedule 22.07.2013
comment
Наполовину связанный: 1 бит на bool в Array C ++, если кому-то интересно, почему вы можете получить ссылка на bool в массиве bool в стиле C. Обратите внимание, что массивы bool[] не упаковываются с 1 битом на логическое значение, как vector<bool>.   -  person jrh    schedule 04.11.2018
comment
советует избегать вектора ‹bool›, поскольку это не контейнер STL, и он на самом деле не содержит bool. - это меня раздражает. Что в нем тогда, отвлекающие маневры? Тип C ++ bool не имеет стандартизированной реализации, вы не можете полагаться на какую-либо конкретную его функцию (в стандартном переносимом исходном коде C ++), за исключением использования его как bool, который должен работать, и который также хорошо работает с vector<bool>. Его размер не определен, и vector<bool> просто переводит его на другой уровень (без адреса), чтобы получить особую выгоду от оптимизации размера. Рекомендовать вообще избегать - это как: раздувать ПО.   -  person Ped7g    schedule 09.09.2019


Ответы (6)


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

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

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

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

person Mark B    schedule 22.07.2013
comment
есть ли у нас какой-либо другой тип данных, для которого любой другой контейнер STL специализирован или явно вызван? - person P0W; 22.07.2013
comment
Применимо ли это к C ++ 11 std :: array ‹bool›? - person Sergio Basurco; 04.12.2015
comment
@chuckleplant нет, std::array - это просто шаблонная оболочка вокруг необработанного массива T[n] с некоторыми вспомогательными функциями, такими как size(), семантика копирования / перемещения и итераторы, добавленные, чтобы сделать его совместимым с STL - и (к счастью) он не нарушает свои собственные принципы (обратите внимание на мой скептицизм по отношению к ним :) «специализируются» на «bool». - person underscore_d; 19.12.2015
comment
Просто придирка - sizeof (bool) не обязательно должен быть байтом. stackoverflow.com/ questions / 4897844 / - person Uri Raz; 28.12.2019

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

person Ivan Smirnov    schedule 22.07.2013
comment
@PrashantSrivastava deque<bool> не является специализированным, так что это буквально просто deque, содержащая bools. - person Konrad Rudolph; 22.07.2013
comment
@PrashantSrivastava vector<bool> имеет конкретную реализацию шаблона. Я думаю, другие контейнеры STL, такие как deque<bool>, этого не делают, поэтому они содержат bool-ы, как и любые другие типы. - person Ivan Smirnov; 22.07.2013
comment
Вот вопросы, которые задают похожую вещь в ржавчине, где они запрещают одноразрядные логические значения stackoverflow.com/questions/48875251 / - person andy boot; 21.02.2018

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

У Скотта Мейерса длинный пункт 30 в Более эффективный C ++ < / strong> о прокси-классах. Вы можете пройти долгий путь к почти имитации встроенных типов: для любого заданного типа 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: no vector<bool> не является контейнером, потому что стандарт требует наличия реальной ссылки, но его можно заставить вести себя почти как контейнер, по крайней мере, намного ближе к C ++ 11 (авто), чем в C ++ 98.

person TemplateRex    schedule 22.07.2013

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

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

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


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

person Trevor Hickey    schedule 29.02.2016

Посмотрите, как это реализовано. 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'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.

В этом коде действительно есть недостаток, попробуйте запустить:

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> все еще есть ограничения, из-за которых в некоторых случаях он ведет себя иначе.

person Alexander Oh    schedule 22.07.2013

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

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

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

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

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

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

person kvv    schedule 22.07.2013
comment
Это не дает прямого ответа на вопрос. В лучшем случае от читателя требуется сделать вывод, какие вещи, описанные в этом общем обзоре, делают его не-STL. - person underscore_d; 19.12.2015