web-dev-qa-db-ja.com

vector <bool>がSTLコンテナではないのはなぜですか?

Scott Meyersの本の項目18 効果的なSTL:標準テンプレートライブラリの使用を改善する50の特定の方法 STLコンテナではなく、boolsを実際に保持していないため、vector <bool>を避けるように言っています。

次のコード:

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>の代替として使用できますか?

誰でもこれを説明できますか?

81
P0W

スペース最適化の理由から、C++標準(C++ 98まで)は、各ブールが通常のブールのように1バイトではなく1ビットのスペースのみを使用する特別な標準コンテナとしてvector<bool>を明示的に呼び出します。 (一種の「動的ビットセット」の実装)。この最適化と引き換えに、通常の標準コンテナのすべての機能とインターフェースを提供するわけではありません。

この場合、バイト内のビットのアドレスを取得できないため、operator[]などはbool&を返すことはできませんが、代わりに特定のビットを操作できるプロキシオブジェクトを返します問題です。このプロキシオブジェクトはbool&ではないため、そのような演算子を「通常の」コンテナで呼び出した場合のように、そのアドレスをbool*に割り当てることはできません。これは、bool *pb =&v[0];が有効なコードではないことを意味します。

一方、dequeにはそのような特殊化が呼び出されないため、各ブールは1バイトを使用し、operator[]から返される値のアドレスを取得できます。

最後に、MS標準ライブラリの実装は、dequeに小さなチャンクサイズを使用するという点で(ほぼ間違いなく)最適ではないことに注意してください。つまり、dequeを代用として使用することは必ずしも正しい答えではありません。

92
Mark B

vector<bool>は、値に1ビットのみを使用する圧縮形式のブール値を含みます(bool []配列のように8ビットではありません)。 C++のビットへの参照を返すことはできないため、メモリ内の一部のビットへのインターフェイスを提供し、標準の演算子とキャストを使用できる特別なヘルパー型「ビット参照」があります。

24
Ivan Smirnov

問題は、_vector<bool>_が真の参照ではなくプロキシ参照オブジェクトを返すため、C++ 98スタイルのコード_bool * p = &v[0];_がコンパイルされないことです。ただし、_auto p = &v[0];_を含む最新のC++ 11をコンパイルするには、_operator&_も プロキシポインターオブジェクトを返す を使用します。 Howard Hinnantはブログ投稿を作成しました。このようなプロキシ参照とポインターを使用する際のアルゴリズムの改善点について詳しく説明しています。

Scott Meyersには、プロキシクラスについて より効果的なC++ の長いItem 30があります。 almost組み込みの型を真似るには長い道のりがあります:与えられた型Tに対して、プロキシのペア(例えば_reference_proxy<T>_と_iterator_proxy<T>_)はreference_proxy<T>::operator&()iterator_proxy<T>::operator*()が互いに逆であるという意味で、相互に一貫性があるようにします。

ただし、ある時点で、_T*_または_T&_のように動作するようにプロキシオブジェクトをマップし直す必要があります。イテレータプロキシの場合、operator->()をオーバーロードし、すべての機能を再実装せずにテンプレートTのインターフェイスにアクセスできます。ただし、参照プロキシの場合、operator.()をオーバーロードする必要があり、現在のC++では許可されていません(ただし、Sebastian Redl そのような提案を提示しました on BoostCon 2013)。参照プロキシ内で.get()メンバーのような冗長な回避策を作成するか、参照内でTのすべてのインターフェイスを実装できます(これは_vector<bool>::bit_reference_に対して行われます) 、しかし、これは組み込みの構文を失うか、型変換のための組み込みのセマンティクスを持たないユーザー定義の変換を導入します(引数ごとに最大1つのユーザー定義の変換を持つことができます)。

TL; DR:no _vector<bool>_はコンテナではありません。これは、標準では実際の参照が必要ですが、少なくともC++ 11に近いコンテナのように動作するようにすることができるためです(自動)C++ 98より。

21
TemplateRex

多くの人がvector<bool>スペシャライゼーションは間違いです。

論文で 「C++ 17のVestigialライブラリパーツの廃止」
ベクトル部分特殊化の再検討への提案があります。

コンテナの要件を満たさないstd :: vectorのbool部分特殊化の長い歴史があり、特に、その反復子がランダムアクセス反復子の要件を満たさないことがあります。このコンテナを廃止する以前の試みは、C++ 11、 N2204 で拒否されました。


拒否の理由の1つは、テンプレートの特定の特殊化を非推奨にすることの意味が明確ではないことです。それは注意深い言葉遣いで対処できます。より大きな問題は、ベクトルの(パックされた)特殊化が、標準ライブラリのクライアントが真に求める重要な最適化を提供するが、もはや利用できなくなることです。 N205 のような代替機能が提案され受け入れられるまで、標準のこの部分を非推奨にすることはできそうにありません。残念ながら、現在、Library Evolution Working Groupに提供されている改訂版の提案はありません。

7
Trevor Hickey

実装方法を見てください。 STLはテンプレート上で大きく構築されるため、ヘッダーにはコードが含まれています。

たとえば、stdc ++実装 here を見てください。

また、stl準拠のビットベクトルではないにもかかわらず、興味深いのは、 here からのllvm :: BitVectorです。

llvm::BitVectorの本質はreferenceと呼ばれるネストされたクラスであり、BitVectorを動作させる適切な演算子オーバーロードはvectorと同様に動作しますが、いくつかの制限があります。以下のコードは、BitVectorがreferenceというクラスを非表示にして、各値に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;      
};

このコードにはNiceプロパティがあります:

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>にはまだ制限があり、someの場合の動作が異なります。

4
Alex

これは http://www.cplusplus.com/reference/vector/vector-bool/ から来ています

Boolのベクターこれは、bool型の要素に使用され、スペースを最適化するベクターの特殊バージョンです。

以下の変更を加えて、それはvectorの特殊化されていないバージョンのように動作します。

  • ストレージは必ずしもブール値の配列ではありませんが、ライブラリの実装はストレージを最適化して各値が
    単一ビットに保存されます。
  • 要素はアロケーターオブジェクトを使用して構築されませんが、その値は内部ストレージの適切なビットに直接設定されます。
  • メンバー関数の反転とメンバー交換の新しい署名。
  • 特別なメンバータイプ、参照、コンテナの内部ストレージ内の個々のビットにアクセスするインターフェイスであるクラス
    ブール参照をエミュレートします。逆に、メンバータイプconst_referenceは単純なブールです。
  • コンテナで使用されるポインタとイテレータのタイプは、必ずしもポインタでも適合イテレータでもありませんが、
    予想される動作のほとんどをシミュレートします。

これらの変更により、この特殊化への風変わりなインターフェースが提供され、処理よりもメモリの最適化が優先されます(ニーズに合っている場合とそうでない場合があります)。いずれの場合でも、boolのベクターの特殊化されていないテンプレートを直接インスタンス化することはできません。この範囲を回避するための回避策は、異なる型(char、unsigned char)またはコンテナ(dequeなど)を使用してラッパー型を使用するか、特定のアロケータ型に特化することです。

bitsetは、固定サイズのビット配列に対して同様の機能を提供するクラスです。

3
kvv