Forgotten C++

C++を長く使っていると、やはりどうしてもよく使う機能と使わない機能がでてきます。 みなさんは、C++にこんな機能があったことを覚えているでしょうか? もしかしたら、すっかり忘れていた機能があるかもしれません。

explicit

explicitは、暗黙の型変換を防ぐためにコンストラクタにつける修飾子です。 具体例で見てみましょう。

001 class ArrayA {
002 public:
003   ArrayA(int size);
004   
005   // ...実装など
006 };
007 
008 class ArrayB {
009 public:
010   explicit ArrayB(int size);
011   
012   // ...実装など
013 };
014 
015 int main(void) {
016   ArrayA a(10);
017   ArrayB b(10);
018   
019   // これが通ってしまう!
020   if(a == 100) {
021     // ...
022   }
023   
024   // これは通らない
025   if(b == 100) {
026     // ...
027   }
028   
029   return 0;
030 }

明らかに、20行目の条件式はおかしいですね。配列のArrayAクラスと、単なる数値は比較しようがありません。

にもかかわらず、これが通ってしまうのは、ひとえに「暗黙の型変換」のせいです。 20行目の条件式では、まずコンパイラはArrayAクラスとint型を引数に取るoperator==を探します。 しかしそんなものはどこにもないので、次にコンパイラは暗黙の型変換を探します。 すると、int型からArrayA型への型変換は、コンストラクタを利用して実行可能であることが分かり、 コンパイラはこの式を

020   if(a == ArrayA(100)) {

と解釈して、コンパイルを通してしまいます。

これを防ぐためのキーワードが、explicitです。 これが指定されたコンストラクタでは、それを使用しての暗黙の型変換が禁止されます。 std::vectorのコンストラクタなどに、実際の使用例があります。

mutable

mutableは、クラスのメンバ変数のみに指定可能な記憶クラス指定子です。 これを指定したメンバ変数は、constなメンバ関数内であっても変更可能になります。

実例を見てみましょう。

001 class Hoge {
002 private:
003   int value_;
004   mutable int mvalue_;
005 public:
006   void foo(void) const {
007     // これはダメ
008     // value_ = 0;
009     
010     // これはOK
011     mvalue_ = 0;
012   }
013 };

mutable指定子は、const指定子やstatic指定子とは同時に指定できません。まあ、当然ですね。

あまり多用はしない方がいいとは思いますが、ここぞというときにはconst_castと並んで便利でしょう。

ビットフィールド

構造体やクラスに、例えばbool型のメンバ変数がたくさんあるときなどに、このビットフィールドは活躍します。 以下のような構文で使用します。

001 struct Hoge {
002   unsigned a:1;
003   bool     b:1;
004   signed   c:2;
005   unsigned d:4;
006 };

このように記述すると、変数aは符号なしの1ビット長整数、 変数bは1ビットのbool型変数、 変数cは符号つきの2ビット長整数、 変数dは符号なしの4ビット長整数になります。 コンパイラによっては、構造体のサイズを小さくできるかもしれません。

ちなみに、VCでは構造体のアライメントの関係でsizeof(Hoge)は1ではなく4になります。 あまり効果を実感できませんね……。

VCで効果を実感したければ、これくらいやる必要があります。

001 struct Hoge {
002   unsigned a:1;
003   unsigned b:1;
004   signed   c:2;
005   unsigned d:4;
006   unsigned e:1;
007   unsigned f:1;
008   signed   g:2;
009   unsigned h:4;
010   unsigned i:1;
011   unsigned j:1;
012   signed   k:2;
013   unsigned l:4;
014   unsigned m:1;
015   unsigned n:1;
016   signed   o:2;
017   unsigned p:4;
018 };

ここまでメンバ変数を増やしても、sizeof(Hoge)は4です。 これらの変数を仮に全部char型にするとsizeof(Hoge)は16になりますので、 これで効果を実感できるのではないかと思います。

ただし、このビットフィールドに対しては単項&演算子を使用できません。 つまり、ビットフィールドへのポインタというものは存在し得ないのです。 また、ビットフィールドへの参照は、const参照でない限り不可能です。

また、地味な注意点ですが、符号付きの1ビット長整数を宣言した場合、 ほとんどの処理系でその変数に代入できる値は0と-1であり、0と1ではない点に注意してください。 16ビット長や32ビット長の整数ではこんな細かいところは気にもかけませんが、 ビット長が短くなると整数の内部表現は結構重要な問題だったりします。 また他の注意点として、VCでは、bool型のビットフィールドは問答無用で1バイトになってしまうようです。

VCでのビットフィールドの内部表現について

いろいろ調べてみたのですが、どうやらVCではビットフィールドはあまり使い物になりそうもありません。 VCでのビットフィールドの内部表現は、以下のようになっているみたいです。 内部表現の図……表示してください

分かり難いですが、bitfield用の領域は4バイト単位で確保され、 なおかつ余ったbitfield用領域にはpadding(詰め物)がされるだけで再利用もされないようなのです。 上のような構造体のsizeofをVCで調べると、なんと32というとんでもない大きさを吐いてくれます。 VCの構造体のアライメントについては、叩けばもっといろいろ出てきそうですが、今回はここら辺で。