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

同カテゴリーの次の記事

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

この記事は、インテル® デベロッパー・ゾーンに公開されている「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* カーネルでも目にしたことがあります (英語)。

このエラーが頻繁に発生する原因は、標準 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 が推奨される理由 (英語)

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

関連記事

  • Parallel Universe マガジンParallel Universe マガジン Parallel Universe へようこそ。 米国インテル社が四半期に一度オンラインで公開しているオンラインマガジンです。インテルの技術者によるテクノロジーの解説や、最新ツールの紹介など、並列化に関する記事を毎号掲載しています。第1号からのバックナンバーを PDF 形式で用意しました、ぜひご覧ください。 12 […]
  • 比較関数の罠比較関数の罠 この記事は、インテル® デベロッパー・ゾーンに公開されている「The Evil within the Comparison Functions」の日本語参考訳です。 この記事の PDF […]
  • 並列プログラミングにおけるロックの効率的な使用並列プログラミングにおけるロックの効率的な使用 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Locks Effectively in Parallel Programming […]
  • マルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発するマルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発する コードの並列化にはさまざまな手法があります。この記事では、インテル® Parallel Composer で利用可能な手法の概要を説明し、各手法の主な長所を比較します。インテル® Parallel Composer は Windows* 上の C/C++ を使用した開発のみを対象としていますが、これらの手法の多くは Fortran や […]
  • インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Intel® AVX Realization Of IIR Filter For Complex Float Data」の日本語参考訳です。 はじめに この記事では、インテル® AVX の SIMD (Single Instruction […]