プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 15

その他インテル® DPC++/C++ コンパイラー特集

この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。


15. コードではできるだけ enum class を使用する

私の手元にあるこのエラーのコード例はすべて大きく、その中で一番小さいものを選びましたが、それでもまだ大きいです。

この問題は、Source* SDK プロジェクトで見つかりました。このエラーは、次の PVS-Studio 診断によって検出されます。

V556 The values of different enum types are compared: Reason == PUNTED_BY_CANNON. (V556 異なる enum 型の値が比較されました: Reason == PUNTED_BY_CANNON。)

enum PhysGunPickup_t
{
  PICKED_UP_BY_CANNON,
  PUNTED_BY_CANNON,
  PICKED_UP_BY_PLAYER,
};

enum PhysGunDrop_t
{
  DROPPED_BY_PLAYER,
  THROWN_BY_PLAYER,
  DROPPED_BY_CANNON,
  LAUNCHED_BY_CANNON,
};

void CBreakableProp::OnPhysGunDrop(...., PhysGunDrop_t Reason)
{
  ....
  if( Reason == PUNTED_BY_CANNON )
  {
    PlayPuntSound(); 
  }
  ....
}

説明

Reason 変数は、PhysGunDrop_t 型の列挙です。この変数は、別の列挙の名前付き定数 PUNTED_BY_CANNON と比較されますが、この比較は明らかに論理エラーです。

これはよくある問題パターンです。Clang、TortoiseGit、Linux* カーネルでも目にしたことがあります (http://www.viva64.com/en/examples/V556/)。

このエラーが頻繁に発生する原因は、標準 C++ では列挙は型安全ではなく、何と何を比較すべきか混乱しやすいためです。

正しいコード

このコードの正しいバージョンは分かりませんが、おそらく、PUNTED_BY_CANNON を DROPPED_BY_CANNON または LAUNCHED_BY_CANNON に置き換えるべきだと考えられます。ここでは、LAUNCHED_BY_CANNON と仮定します。

if( Reason == LAUNCHED_BY_CANNON )
{
  PlayPuntSound(); 
}

推奨事項

C++ で記述する場合は簡単です。enum class を使用することを推奨します。コンパイラーは、異なる列挙の値の比較を許可しないため、kg と cm を比較するようなことはありません。

C++ の一部の機能について、私はあまり信用していません。例えば、auto キーワードです。このキーワードは、使用しすぎると有害であると考えています。プログラマーは、コードの記述よりも読解に多くの時間を費やため、プログラムコードを分かりやすいものにする必要があります。C 言語では、変数は関数の先頭で宣言されるため、関数の途中や最後のコードを編集する場合、Alice 変数がどんな変数であるのか理解することは必ずしも容易ではありません。そのため、さまざまな変数の命名規則が存在します。例えば、pfAlice のプリフィクス pf は、float 型へのポインターを示します。

C++ では、必要なときに変数を宣言でき、それが良いコーディング手法であると考えられています。変数名にプリフィクスやサフィックスを追加することは一般的ではありません。そこで登場したのが auto キーワードで、プログラマーは “auto Alice = Foo();” のような複数の謎の構造を使い始めました。そして、Alice って一体何?! となるわけです。

脱線しましたが、新しい機能は良くも悪くもあるということを皆さんに示したかったのです。しかし、enum class には利点しかないと私は信じています。

enum class を使用する場合、名前付き定数がどの列挙に属するか明示的に指定する必要があります。これは、新しいエラーからコードを守ります。次のようなコードになります。

enum class PhysGunDrop_t
{
  DROPPED_BY_PLAYER,
  THROWN_BY_PLAYER,
  DROPPED_BY_CANNON,
  LAUNCHED_BY_CANNON,
};

void CBreakableProp::OnPhysGunDrop(...., PhysGunDrop_t Reason)
{
  ....
  if( Reason == PhysGunDrop_t::LAUNCHED_BY_CANNON )
  {
    PlayPuntSound(); 
  }
  ....
}

古いコードの変更にはある程度の困難が伴いますが、今後新しいコードでは enum class を使用することを推奨します。そうすることで、利点が得られます。

ここでは、enum class について詳しく説明しません。C++11 の新機能の詳細は、以下のリンクを参照してください。

  1. Wikipedia* の C++11 に関するトピック: 強い型付けの列挙型
  2. Cppreference: 列挙型の宣言 (英語)
  3. Stack Overflow: enum ではなく enum class が推奨される理由 (英語)

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました