私はC++ 11/14でほぼ独占的に作業しており、通常、次のようなコードを見るとうんざりします。
std::int64_t mArray;
mArray |= someMask << 1;
これは単なる例です。私は一般的にビット単位の操作について話しています。 C++では、本当に何か意味がありますか?上記は心ゆがみでエラーが発生しやすいですが、std::bitset
を使用すると次のことができます。
std::bitset
のサイズを必要に応じてテンプレートパラメータを調整し、残りの部分を実装に任せることで、より簡単に変更できます。std::bitset
または他のデータコンテナーと同様の方法でstd::array
を書き込みます。私の質問です。 not下位互換性以外のプリミティブ型に対してstd::bitset
を使用する理由はありますか?
論理的(非技術的)な観点からは、利点はありません。
プレーンなC/C++コードは、適切な「ライブラリ構造」内にラップできます。そのようなラッピングの後、「これがそれより有利であるかどうか」という問題は議論の余地のある質問になります。
速度の観点から見ると、C/C++では、ライブラリ構造がラップするプレーンコードと同じくらい効率的なコードを生成できるようにする必要があります。ただし、これは以下の制約を受けます。
この種の非技術的な議論を使用すると、「不足している関数」は誰でも追加できるため、不利な点としては数えられません。
ただし、組み込みの要件と制限は、追加のコードでは克服できません。以下では、std::bitset
のサイズはコンパイル時の定数であるため、不利とは見なされませんが、それでもユーザーの選択に影響を与えるものです。
美的観点(読みやすさ、保守のしやすさなど)の点で違いがあります。
ただし、std::bitset
コードが単純なCコードよりもすぐに勝つことは明らかではありません。 std::bitset
の使用によってソースコードの人間の品質が向上したかどうかを判断するには、より大きなコード(おもちゃのサンプルではなく)を調べる必要があります。
ビット操作の速度は、コーディングスタイルによって異なります。コーディングスタイルはC/C++ビット操作の両方に影響し、以下で説明するように、std::bitset
にも同様に適用できます。
operator []
を使用して一度に1ビットを読み書きするコードを書き込む場合、操作するビットが複数ある場合は、これを複数回行う必要があります。 Cスタイルのコードについても同じことが言えます。
ただし、bitset
には、operator &=
、operator <<=
などの他の演算子もあり、ビットセットの全幅で動作します。基盤となる機械は、しばしば32ビット、64ビット、時には128ビット(SIMDを使用)で(同じCPUサイクル数で)動作できるため、このようなマルチビット操作を利用するように設計されたコード「ルーピー」ビット操作コードよりも高速である可能性があります。
一般的な考え方は SWAR(レジスタ内のSIMD) と呼ばれ、ビット操作のサブトピックです。
一部のC++ベンダーは、SIMDを使用して64ビットと128ビットの間にbitset
を実装する場合があります。一部のベンダーはそうしないかもしれません(しかし最終的にはそうするかもしれません)。 C++ベンダーのライブラリが何をしているかを知る必要がある場合、唯一の方法は逆アセンブリを調べることです。
std::bitset
に制限があるかどうかについて、2つの例を挙げます。
std::bitset
のサイズはコンパイル時にわかっている必要があります。動的に選択されたサイズのビットの配列を作成するには、std::vector<bool>
を使用する必要があります。std::bitset
の現在のC++仕様では、Mビットのより大きなbitset
からNビットの連続したスライスを抽出する方法が提供されていません。最初のものは基本的なものです。つまり、動的サイズのビットセットを必要とする人は、他のオプションを選択する必要があります。
標準のbitset
が拡張可能でない場合でも、タスクを実行するために ある種のアダプタ を記述できるため、2つ目の方法は克服できます。
std::bitset
からそのままでは提供されない特定のタイプの高度なSWAR操作があります。 ビット順列についてのこのウェブサイト でこれらの操作について読むことができます。いつものように、std::bitset
の上で動作するこれらを自分で実装できます。
パフォーマンスに関する議論について。
一つの警告:多くの人々が、標準ライブラリの(something)が単純なCスタイルのコードよりもはるかに遅い理由を尋ねます。ここではマイクロベンチマークの前提知識を繰り返さないでください。ただし、このアドバイスがあります。「リリースモード」でベンチマークを行い(最適化を有効にして)、コードが削除されていないことを確認してください 除去(デッドコード)除去) または ループから外れた状態(ループ不変コードモーション) であること。
一般に、誰かが(インターネット上で)マイクロベンチマークを正しく実行していたかどうかを判断できないため、信頼できる結論を得るには、独自のマイクロベンチマークを実行して詳細を文書化し、パブリックレビューと批評に提出するしかありません。他の人が以前に行ったようなマイクロベンチマークをやり直すことは害にはなりません。
これは確かにすべての場合に当てはまるわけではありませんが、アルゴリズムがCスタイルのビット調整の効率に依存して、パフォーマンスを大幅に向上させる場合があります。私の頭に浮かぶ最初の例は、チェスエンジンなどを高速化するために、ボードゲームの位置の巧妙な整数エンコーディングである bitboards を使用することです。チェス盤は常に8 * 8なので、整数型の固定サイズは問題ありません。
簡単な例として、次の関数(Ben Jacksonによる この回答は、Ben Jacksonによる から取得)を検討してください。
// return whether newboard includes a win
bool haswon2(uint64_t newboard)
{
uint64_t y = newboard & (newboard >> 6);
uint64_t z = newboard & (newboard >> 7);
uint64_t w = newboard & (newboard >> 8);
uint64_t x = newboard & (newboard >> 1);
return (y & (y >> 2 * 6)) | // check \ diagonal
(z & (z >> 2 * 7)) | // check horizontal -
(w & (w >> 2 * 8)) | // check / diagonal
(x & (x >> 2)); // check vertical |
}