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

103

В пункте 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>?

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

P0W
источник
22
Это была дизайнерская ошибка C ++ 98, которая теперь сохранена для совместимости.
Oktalist
8
@ g-makulik, дело не в том, что использование его не компилируется, просто вы не можете сохранить адрес элемента в указателе на bool, так как у элемента нет своего адреса.
Крис
2
Возможно, это поможет: stackoverflow.com/questions/670308/alternative-to-vectorbool
Крис
1
@ g-makulik std::vector<bool> v;скомпилирует. &v[0]не будет (взяв временный адрес).
Oktalist
4
vector<bool>имеет плохую репутацию, но не совсем оправданно: isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

Ответы:

120

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

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

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

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

Марк Б
источник
5
есть ли у нас какой-либо другой тип данных, для которого любой другой контейнер STL специализирован или явно вызван?
P0W
3
Применимо ли это к C ++ 11 std :: array <bool>?
Серхио Басурко,
4
@chuckleplant нет, std::arrayэто просто шаблонная оболочка вокруг необработанного массива T[n]с некоторыми вспомогательными функциями, такими как size()семантика копирования / перемещения и итераторами, добавленными, чтобы сделать его совместимым с STL - и (к счастью) он не нарушает свои собственные принципы (обратите внимание на мои скептицизм этих :) "специализируется" на " bool".
underscore_d
Просто придирка - sizeof (bool) не обязательно является байтом. stackoverflow.com/questions/4897844/…
Ури Раз
30

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

Иван Смирнов
источник
1
@PrashantSrivastava deque<bool>не является специализированным, так что это буквально просто deque, содержащий bools.
Конрад Рудольф
@PrashantSrivastava vector<bool>имеет конкретную реализацию шаблона. Я предполагаю, что другие контейнеры STL, такие как deque<bool>, не содержат, поэтому они содержат bool-ы, как и любые другие типы.
Иван Смирнов
Вот вопросы, которые задают похожую вещь в ржавчине, где они запрещают одноразрядные логические значения stackoverflow.com/questions/48875251/…
andy boot
25

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

TemplateRex
источник
10

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

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

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


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

Тревор Хики
источник
5

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

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

Также интересно , даже если не СТЛ соответствующий битовый вектор является 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>которые заставляют его вести себя по-другому в некоторых случаях.

Александр О
источник
3

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

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

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

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

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

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

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