C++でコンパイル時エラーチェックの強化

コードにバグはつきものですが、バグはなるべく早く見つけたいもの。assertによるチェックはもちろん有効ですが、評価されるのは動作時です。C++11ではstatic_assertが導入され、コンパイル時のチェック機能が強化されています。コンパイル時チェックは実行時の性能に影響を与えることもなく、気軽に挿入することもできますね


static_assertはコンパイル時に評価されるため、当然定数式しか評価することが出来ません。それでも例えば組み込みコードなどで、ポインタや整数型のサイズをチェックすることができます。

#include <cstdint>

static_assert(sizeof(uint32_t) == 4, "sizeof(uint32_t) != 4");
static_assert(sizeof(uint32_t) == 2, "sizeof(uint32_t) != 2");   // Error

上記コードをコンパイルしようとすると以下のようにエラーとなります

% clang++ static_assert_usage.cce
static_assert_usage.cc:4:1: error: static_assert failed due to requirement 'sizeof(unsigned int) == 2' "sizeof(uint32_t) != 2"
static_assert(sizeof(uint32_t) == 2, "sizeof(uint32_t) != 2");
^             ~~~~~~~~~~~~~~~~~~~~~
1 error generated.

これだけではそれほど有用ではないかもしれませんが、同時に導入された様々な型チェックとともに用いることでさらに有用な検証が可能となります。

例えば、ネットワークを介したデータのやり取りでは、データ構造をstructで定義し、そこに受信したデータをmemcpy()して処理する場面もあると思いますが、C++では構造体 (クラス)に仮装関数や代入演算子定義などがあるとmemcpy()でデータをコピーすることはできません。うっかりそのような実装を混ぜ込むことを防止するためにis_trivially_copyableを用いたチェックを利用することができます

#include <cstdint>
#include <type_traits>

struct MyCommand {
  uint32_t GetId() const { return id; }
  uint32_t GetCommand() const { return command; }

 private:
  uint32_t id;
  uint32_t command;
};

struct MyCommand2 {
  virtual uint32_t GetId() const { return id; }
  uint32_t GetCommand() const { return command; }

 private:
  uint32_t id;
  uint32_t command;
};

static_assert(std::is_trivially_copyable<MyCommand>::value,
              "MyCommand is not trivially_copyable");  // OK
static_assert(std::is_trivially_copyable<MyCommand2>::value,
              "MyCommand2 is not trivially_copyable");  // Error

static_assert(sizeof(MyCommand) == 8,
              "MyCommand size is not 8");   // OK
static_assert(sizeof(MyCommand2) == 8,
              "MyCommand2 size is not 8");  // Error

上記の例で、MyCommandは問題ありませんが、MyCommand2は仮装関数を含んでいるためエラーとなります。実際sizeofによるチェックでもMyCommandのサイズは8ですが、MyCommand2は異なります

おすすめ