Чому вектор <bool> не є контейнером STL?


99

Пункт 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 []повинен бути тип return T&, але чому це особливий випадок vector<bool>?

З чого vector<bool>насправді складається?

У пункті далі сказано:

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

Чи можна це використовувати як альтернативу vector<bool>?

Хто-небудь може пояснити це?


22
Це була помилка дизайну в C ++ 98, тепер збережена для сумісності.
Oktalist

8
@ g-makulik, Справа не в тому, що його використання не компілюється, просто ви не можете зберегти адресу елемента в покажчику bool, оскільки елемент не має власної адреси.
chris

2
Можливо, це допоможе: stackoverflow.com/questions/670308/alternative-to-vectorbool
chris

1
@ g-makulik std::vector<bool> v;скомпілює. &v[0]не буде (приймаючи адресу тимчасового).
Oktalist

4
vector<bool>має погану репутацію, але не цілком виправдано: isocpp.org/blog/2012/11/on-vectorbool
TemplateRex

Відповіді:


114

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

У цьому випадку, оскільки ви не можете взяти адресу біта в байті, такі речі, як operator[]не можуть повернути a, bool&а замість цього повертають проксі-об'єкт, який дозволяє маніпулювати конкретним розглянутим бітом. Оскільки цей проксі-об'єкт не є 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

Просто підбір гніди - розмір (bool) не обов'язково байт. stackoverflow.com/questions/4897844/…
Урі Раз

30

vector<bool>містить булеві значення в стиснутому вигляді, використовуючи лише один біт для значення (а не 8, як це роблять масиви bool []). Повернути посилання на біт у c ++ неможливо, тому існує спеціальний допоміжний тип, "посилання на біт", який надає вам інтерфейс до деякого біта в пам'яті та дозволяє використовувати стандартні оператори та закиди.


1
@PrashantSrivastava deque<bool>не є спеціалізованим, тому буквально це просто приналежність, що тримає боліди.
Конрад Рудольф

@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 (auto), ніж у C ++ 98.


10

Багато хто вважає vector<bool>спеціалізацію помилкою.

У статті "Знищення частин вестигіальної бібліотеки в C ++ 17"
є пропозиція переглянути повторну часткову спеціалізацію вектора .

Існує довга історія часткової спеціалізації bool: std :: vector, яка не задовольняє вимоги контейнера, і, зокрема, його ітератори, які не задовольняють вимогам ітератора довільного доступу. Попередня спроба застаріти цей контейнер була відхилена для C ++ 11, N2204 .


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


5

Подивіться, як це реалізовано. STL багато в чому базується на шаблонах, і тому заголовки містять код, який вони роблять.

наприклад , погляд на STDC ++ реалізації тут .

Також цікаво , навіть якщо не СТЛ відповідний бітовий вектор є LLVM :: BitVector з тут .

суть llvm::BitVectora - це вкладений клас, який називається 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/

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

Він поводиться як неспеціалізована версія вектора із наступними змінами:

  • Зберігання не обов'язково є масивом значень bool, але реалізація бібліотеки може оптимізувати сховище, щоб кожне значення
    зберігалося в одному біті.
  • Елементи не будуються за допомогою об'єкта розподілювача, але їх значення безпосередньо встановлюється у відповідному біті у внутрішній пам'яті.
  • Перевернення функції члена та новий підпис для обміну учасниками.
  • Спеціальний тип члена, посилання, клас, який отримує доступ до окремих бітів у внутрішній пам’яті контейнера з інтерфейсом, який
    емулює посилання bool. І навпаки, тип учасника const_reference - це звичайний bool.
  • Типи покажчиків та ітераторів, що використовуються контейнером, не обов'язково є ні вказівниками, ні відповідними ітераторами, хоча вони
    повинні імітувати більшість їх очікуваної поведінки.

Ці зміни забезпечують химерний інтерфейс цієї спеціалізації та сприяють оптимізації пам’яті над обробкою (яка може відповідати вашим потребам або не відповідати їм). У будь-якому випадку неможливо створити екземпляр неспеціалізованого шаблону вектора для bool безпосередньо. Вирішення проблем, щоб уникнути цього діапазону від використання іншого типу (char, непідписаний char) або контейнера (наприклад, deque) для використання типів обгортки або подальшої спеціалізації для конкретних типів розподільника.

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


1
Це не відповідає безпосередньо на запитання. У кращому випадку це вимагає від читача висновку, які речі, пояснені в цьому загальному підсумку, роблять його не-STL.
underscore_d
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.