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

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

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


24. override 指定子と final 指定子を使用する

MFC ライブラリーから抜粋した以下のコードについて考えてみます。このエラーは、次の PVS-Studio 診断によって検出されます。

V301 Unexpected function overloading behavior. See first argument of function ‘WinHelpW’ in derived class ‘CFrameWndEx’ and base class ‘CWnd’. (V301 予期しない関数の多重定義です。派生クラス ‘CFrameWndEx’ と基本クラス ‘CWnd’ の関数 ‘WinHelpW’ の第 1 引数を確認してください。)

class CWnd : public CCmdTarget {
  ....
  virtual void WinHelp(DWORD_PTR dwData,
                       UINT nCmd = HELP_CONTEXT);
  ....
};
class CFrameWnd : public CWnd {
  ....
};
class CFrameWndEx : public CFrameWnd {
  ....
  virtual void WinHelp(DWORD dwData,
                       UINT nCmd = HELP_CONTEXT);
  ....
};

説明

仮想関数をオーバーライドする場合、シグネチャーを間違ったり、基本クラスの関数と関係のない新しい関数を定義してしまうことはよくあります。この例には、さまざまなエラーの可能性があります。

  1. オーバーライドされた関数のパラメーターで別の型が使用されています。
  2. オーバーライドされた関数のパラメーター数が異なります。これは、パラメーター数が多い場合、特に重大な問題となります。
  3. オーバーライドされた関数の const 修飾子が異なります。
  4. 基本クラス関数が、仮想クラス関数ではありません。派生クラスの関数が基本クラスのそれをオーバーライドすると仮定されましたが、実際には隠しました。

階層全体の仮想関数シグネチャーを変更する際に、一部の派生クラスを変更し忘れた場合、既存コードの型やパラメーター数の変更でも同様のエラーが発生する可能性があります。

このエラーは、64 ビット・プラットフォームへの移行時に DWORD 型を DWORD_PTR に置き換えたり、LONG 型を LONG_PTR に置き換える場合などに特に起こりやすい過ちです。詳細は、こちら (英語) を参照してください。これはまさに上記のコード例の問題です。

32 ビット・システムでは、DWORDDWORD_PTR はどちらも unsigned long であるため、問題なく動作しますが、64 ビット・システムでは DWORD_PTRunsigned __int64 であるためエラーになります。

正しいコード

class CFrameWndEx : public CFrameWnd {
  ....
  virtual void WinHelp(DWORD_PTR dwData,
                       UINT nCmd = HELP_CONTEXT) override;
  ....
};

推奨事項

このエラーを防ぐ方法があります。次の 2 つの指定子が C++11 で追加されました。

  • override – メソッドが基本クラスの仮想メソッドをオーバーライドすることを示します。
  • final – 派生クラスがこの仮想メソッドをオーバーライドする必要がないことを示します。

この例では、override 指定子を使用して、仮想関数が実際に基本クラス関数をオーバーライドしているかチェックして、オーバーライドしていない場合はエラーを出力するようにコンパイラーに指示します。

CFrameWndEx クラスの WinHelp 関数を決定する際に override を使用すると、アプリケーションの 64 ビット・バージョンをコンパイルする際はエラーとなります。そのため、事前に問題を発見できます。

仮想関数をオーバーライドする場合は、常に override 指定子 (または final 指定子) を使用します。overridefinal の詳細は、以下を参照してください。

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

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