Tarih: C++98, bool değerlerini sıkıştırılmış bit temsilinde saklamak için özel bir kap olarak std::vector<bool>'i tanıttı; her boolean için bir bit ayırarak bir byte yerine. Bu tasarım kararı, büyük bit setlerini işleyen ilk uygulamalar için kritik olan önemli bellek tasarrufu sağlama amacı taşımaktaydı — std::vector<char>'dan sekiz kat daha kompakt. Ancak, bireysel bitlerin ayrı bellek adresleri olmadığı için, C++ referansları bunlara bağlanamaz; bu da referans anlamını simüle etmek için bir proxy referans sınıfı oluşturulmasını gerektirir.
Sorun: C++ standartları, standart kapların reference türü olarak gerçek referanslar (bool&) sağlamasını zorunlu kılarken, std::vector<bool> proxy nesneleri (genellikle reference olarak adlandırılır) döndürmektedir. Bu ihlal, Container kavramı gerekliliklerini bozar ve auto& veya std::is_same_v< decltype(vec[0]), bool& > kullanan genel algoritmaların derlenmesini engeller veya beklenmedik şekilde çalışmasına neden olur. Sonuç olarak, bitişik bellek düzenleri veya elemanlar üzerinde işaretçi aritmetiği bekleyen kod, tanımsız davranış veya mantıksal hatalarla karşılaşır.
std::vector<bool> bits = {true, false}; auto& ref = bits[0]; // ref proxy, bool& değil // bool* p = &bits[0]; // HATA: uygun dönüşüm yok
Çözüm: Komite, bu özelizasyonu anlam ihlali olmasına rağmen, bellek verimliliği faydalarının belirli bir kullanım durumu için katı uyumluluktan daha ağır basmasından dolayı korumuştur. Standart kap semantiği gerektiren geliştiriciler, std::vector<bool> kullanmaktan kaçınmalı ve std::vector<char>, std::deque<bool> veya gerçek referanslar sağlayan boost::dynamic_bitset gibi alternatifler kullanmalıdır; bu alternatiflerin bellek verimliliği konusunda maliyeti vardır.
Bir veri analitiği startup'ı, RAM kullanımını en üst düzeye çıkarmak için milyarlarca mutasyon bayrağını saklayan bir genomik dizilim hizalama algoritması uyguladı. Genel şablon fonksiyonları olan process_flags, herhangi bir konteyneri kabul ediyordu ve bitleri değiştirmek için auto& bayrağı = konteyner[i] kullanıyordu, bool& semantiği varsayıyordu. Üçüncü taraf bir paralel işleme kütüphanesi ile entegrasyon sırasında, derleme başarısız oldu çünkü kütüphanenin özellikler sistemi decltype(flag)'ın bir referans türü olmadığını tespit etti ve std::vector<bool>'ı desteklenmeyen olarak reddetti.
Üç çözüm tartışıldı. İlk olarak, sistemi std::vector<uint8_t> kullanacak şekilde yeniden yapılandırmak. Artıları: Tüm genel kodla anlık uyumluluk ve gerçek referans garantileri. Eksileri: Bellek tüketimi %800 arttı ve sunucularındaki kullanılabilir RAM'yi aştı. İkincisi, process_flags'ı std::vector<bool> için proxy sınıf yöntemlerini kullanarak açıkça özelleştirmek. Artıları: Bellek verimliliğini korur. Eksileri: Çift kod yollarının sürdürülmesini gerektirir ve kapsülleme ihlali olan uygulama ayrıntılarını ortaya çıkarır. Üçüncüsü, bitsleri standart bir konteyner olarak maskelenmeden açıkça işleyen boost::dynamic_bitset'e geçiş yapmak. Artıları: Açık API, gerçek bit manipülasyonu ve herhangi bir proxy sürprizi yok. Eksileri: Harici bağımlılık ekler ve kod tabanındaki API değişikliklerini gerektirir.
Ekip, üçüncü taraf kütüphanenin gereksinimlerinin değişmez olması ve bellek kısıtlamalarının pazarlık edilemez olması nedeniyle boost::dynamic_bitset'i seçti. Geçişten sonra sistem, tür ile ilgili derleme hataları olmadan genomik verileri güvenilir bir şekilde işledi ve hem performansı hem de doğruluğu sağladı.
&vec[0] neden bir derleme hatası veya geçersiz işaretçi üretir ve vec std::vector<bool> olduğunda?Çünkü vec[0] geçici bir proxy nesnesi döndürür, bir bool lvalue değil. Bu geçicinin adresini almak, temel bit deposuna değil, kısa ömürlü bir proxy örneğine işaret eden bir işaretçi verir. Standart kaplarda elemanlar bitişik nesnelerken, std::vector<bool> içinde bitlerin adreslenebilir yerleri yoktur; bu durum, işaretçi aritmetiği ve adres almaya yönelik işlemleri anlamsal olarak geçersiz kılar.
std::vector<bool> vec(10); // bool* p = &vec[0]; // Yanlış oluşmuş
Bir genel lambda [&] ile yakaladığında ve container[i] üzerinde işlem yaptığında, mükemmel yönlendirme decltype(auto) ile proxy türünü, bool& yerine belirler. Eğer lambda bunu bool& bekleyen bir fonksiyona yönlendirirse, proxy nesnesi (genellikle geçici veya dahili bit maskelerini içerir) yanlış bir şekilde bileşen objelerine kopyalanır veya çürüme yapar; bu durum, geçici kopyalara yapılan değişikliklerin orijinal konteyner elemanlarını etkilememesine neden olarak sessiz veri kaybına yol açar.
auto lambda = [](auto&& x) { return std::forward<decltype(x)>(x); }; std::vector<bool> vec = {false}; auto&& ref = lambda(vec[0]); // ref proxy'ye bağlanır ref = true; // proxy geçici kopya ise vec[0]'ı değiştirmeyebilir
İteratörün operator*'u değeri ile bir proxy döndürür; bu, ContiguousIterator için *it'in eleman tipi için bir lvalue referansı sağlaması gerekliliğini ihlal eder. std::vector<bool> iteratörleri sabit zamanlı aritmetiği desteklese de (it += n), temel depolama, bool nesnelerinin bitişik bir dizisi değildir; bu, std::to_address(it) veya &*(it + n) == &*it + n varsayımlarına dayalı işaretçi tabanlı optimizasyonların geçerli kullanımını engeller ve keskin veri paylaşımı ve önbellek satırı ön yükleme varsayımlarını bozar.
static_assert(!std::contiguous_iterator<std::vector<bool>::iterator>); // İteratör Rastgele Erişimdir ancak Bitişik Değildir