4 Kasım 2019 Pazartesi

STL Veri Yapısı - Vector Of Bool - Kullanmayın

Giriş
vector<bool> isminde vector geçmesine rağmen özel bir veri yapısı. 1994 yılında ortaya çıkıyor.
1998 yılında yayınlanan bir yazıda bu sınıfın tam bir container olmadığı belirtiliyor.

Bu Sınıf Neden Sevilmiyor ve Tercih Edilmemeli
Bu sınıf ilk çıktığında amaç bellekten tasarruf etmekti. Ancak daha sonra bellek tasarrufuna çok fazla gerek kalmadı. Sınıfın getirdiği problemler yüzünden bu tarz bir optimizasyon std::array<bool> tarzı bir veri yapısına uygulanmadı. std::array<bool> yerine std::bitset kullanılır.

Problem 1
Bu veri yapısının içindeki nesne bool değil. 
Örnek
Şu kod derleme hatası verir
#include <type_traits>
#include <vector>

int main() {
  std::vector<bool> v{false, false, true, true};
  auto bool_value = v[1];
  static_assert(std::is_same_v<decltype(bool_value), bool>, "");  // Error!
}   
Örnek
std::array<bool> ile std::vector<bool> farkı şöyledir
#include <array>
#include <vector>

int main()
{
  std::vector<int> i_v(4);
  int i_a = *&i_v[3]; // ok

  std::vector<bool> v(4);
  bool a = *&v[3]; // Compile-time error

  std::array<bool,4> w;
  bool b = *&w[3]; // ok
}
Vector of Bool Belleği Farklı Yönetir
Contiguous bellek sunmak zorunda değildir. Açıklaması şöyle.
The behaviour of std::vector<bool> is similar to its primarily template counterpart, except that:

1. std::vector<bool> does not necessarily store its element contiguously .
2. In order to expose its elements (i.e., the individual bits) std::vector<bool> uses a proxy class (i.e., std::vector<bool>::reference). Objects of class std::vector<bool>::reference are returned by std::vector<bool> subscript operator (i.e., operator[]) by value.

std:vector<bool>::data Metodu Sağlamaz
Açıklaması burada.

std:vector<bool>::reference tipi prvalue döner
std::vector<bool>::reference proxy bir sınıftır. Bu sınıfı bitleri dışarıya açmak için kullanılır. [] operatörü veya iterator, bu proxy sınıfı dönerler.

Proxy sınıfı bir değişkene bağlamak doğru değildir. Bu problem en çok auto şeklindeki kullanımlarda ortaya çıkıyor.

1. prvalue değişkenin lvalue değişkene bağlanması
Aşağıdaki kod derlenmez.
std::vector<bool> boolVector(10);
for(auto& i : boolVector)
    std::cout << i;
Buradaki sorun std::vector<bool>::reference tipinin prvalue (temporary) olması. prvalue bir değişkeni lvalue bir dğişkene bağlayamayız. Şöyle yaparsak sorun çıkmıyor.
for (auto i: boolVector)
  std::cout << i;
2. prvalue değişkenin adresinin alınması
Elimizde şöyle bir kod olsun
std::vector<bool> isInfluenced;
Şu kod anlamsız bir değer döner.
for (int i = 0; i < a->isInfluenced.size(); i++)
  std::cout << &(a->isInfluenced[i]) << " ";
Açıklaması şöyle.
The elements of a vector<bool> cannot be directly accessed like this, because they are/may be smaller than a byte, which is the "resolution" of addresses in C++. So many bits/elements are packed into a single memory location on your computer. But that's not why you get these results.

You're sort of observing the address of a temporary. That temporary is some proxy object that provides mutable access to a single bit in your collection.

I say "sort of" because you're not even really taking the address; said proxy object has an operator& that gives you something called a "bit iterator" in some implementations.
3. prvalue değişkenin işaret ettiği vector'ün yok olması
Scott Meyers'in "Effective Modern C++" kitabında şöyle bir örnek veriliyor. Örnekte features() metodu rvalue bir vector<bool> döndürüyor. Aşağıdaki kod sorunsuzdur çünkü rvalue vector'ün 5. elamanı bool'a çevrilerek saklanır.
std::vector<bool> features(const Widget& w);
Widget w;

bool highPriority = features(w)[5];

processWidget(w, highPriority); 
Ancak kodu şöyle yaparsak çalışmaz.
auto highPriority = features(w)[5];
Çünkü std::vector<bool>::reference tipi farklı bir tiptir. Kendi içinde pointer kullanan bir proxy sınıfıdır. Eğer proxy'nin point ettiği vector bellekten silinirse sorun çıkar.
Scott Meyer şöyle bir çözüm öneriyor.
auto highPriority = static_cast<bool>(features(w)[5]);
Daha Kolay Bir Çözüm
Şöyle bir metod tanımlarız.
bool is_high_priority(const Widget& w)
{ return features(w)[5]; }
Daha sonra auto ile bu metodun sonucunu saklarız.
auto highPriority = is_high_priority(w);
swap metodu
Elimizde şöyle bir kod olsun.
std::vector<bool> vb{true, false};
Şöyle yaparız.
vb.swap(vb[0], vb[1]);
iterator metodu
Örnek - İki Vector Nesnesinin Karşılaştırılması
Şöyle yapılabilir.
std::vector<bool> A = {0,1,0,1};
std::vector<bool> B = {0,0,1,1};
std::vector<bool> C(A.size());
std::transform(A.begin(), A.end(), B.begin(), C.begin(), 
               [](bool const &a, bool const &b) { return a == b;});
std::cout << std::count(C.begin(), C.end(), true) << std::endl;
Diğer Seçenekler
Kendi veri yapımızı kullanabiliriz.
class Bool
{
public:

  Bool(): m_value(){}
  Bool( bool value ) : m_value(value){}

  operator bool() const { return m_value;}

  // the following operators are to allow bool* b = &v[0]; (v is a vector here).
  bool* operator& () { return &m_value; }
  const bool * const operator& () const { return &m_value; }

private:

  bool m_value;

};
Şöyle kullanırız.
std::vector<Bool> working_solution(10, false);

working_solution[5] = true;
working_solution[7] = true;








Hiç yorum yok:

Yorum Gönder