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

同カテゴリーの次の記事

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

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


38. 今後は NULL の代わりに nullptr を使用する

新しい C++ 標準では多くの有用な変更がありました。そのいくつかは、大きな利点をもたらすため、今すぐ取り入れるべきであると私は考えています。

その 1 つが、NULL マクロの代替である nullptr (英語) キーワードです。

C++ では、NULL は 0 と定義されています。

これは、単なる糖衣構文のように見えるかもしれません。nullptr でも、NULL でも違いがないと思われるかもしれません。しかし、違うのです! nullptr を使用することで、さまざまなエラーを回避できます。このことを次に示します。

2 つの多重定義関数があるとします。

void Foo(int x, int y, const char *name);
void Foo(int x, int y, int ResourceID);

次の呼び出しについて考えてみます。

Foo(1, 2, NULL);

プログラマーは、1 つ目の関数を呼び出しているつもりですが、実際には、NULL は 0 であり、0 は int 型であるため、1 つ目の関数ではなく 2 つ目の関数が呼び出されます。

nullptr を使用した場合、このような間違いは起こらず、1 つ目の関数が呼び出されます。別のよくある NULL の使用例として、次のようなコードがあります。

if (unknownError)
  throw NULL;

私は、ポインターを渡す例外の生成を疑わしく思っていますが、一部のプログラマーは行っています。上記の例では、プログラマーはこの方法でコードを記述する必要があったのかもしれません。そうすることが良いか、悪いかについての議論はトピックから外れるため、ここでは取り上げません。

ここで重要なことは、プログラマーが不明なエラーの場合に例外を生成し、外部に null ポインターを「渡して」いることです。

実際には、これはポインターではなく int です。その結果、プログラマーが想定しない方法で例外が処理されます。

“throw nullptr;” コードはこの問題を解決してくれますが、私は完全に納得しているわけではありません。

一部のケースでは、nullptr を使用すると、不正なコードはコンパイルしません。

HRESULT 型を返す WinApi 関数があるとします。HRESULT (英語) 型は、ポインターとは関係がありません。しかし、次のような無意味なコードを記述する可能性は十分にあります。

if (WinApiFoo(a, b, c) != NULL)

NULLint 型の 0 で、HRESULTlong 型であるため、このコードはコンパイルします。int 型と long 型の値の比較は可能です。nullptr を使用すると、次のコードはコンパイルしません。

if (WinApiFoo(a, b, c) != nullptr)

コンパイルエラーになることで、プログラマーがエラーに気付き、コードを修正できます。

問題の趣旨はお分かりいただけたかと思います。このような例が多数あります。しかし、その多くは合成された例で、説得力に欠けます。実例はないのでしょうか? あります。ここでは、そのうちのいくつかを紹介します。これらのコードは、あまり洗練されておらず、短くもありません。

MTASA プロジェクトから抜粋した以下のコードについて考えてみます。

RtlFillMemory() (英語) があります。これは、実関数またはマクロです。ここでは、どちらであるかは重要ではありません。memset() 関数に似ていますが、第 2 引数と第 3 引数が入れ替わっています。このマクロは、次のように宣言できます。

#define RtlFillMemory(Destination,Length,Fill) \
  memset((Destination),(Fill),(Length))

FillMemory() というものもあります。これは、RtlFillMemory() です。

#define FillMemory RtlFillMemory

前述のとおり、長く複雑ですが、エラーを含む実際のコード例です。

以下が、FillMemory マクロを使用するコードです。

LPCTSTR __stdcall GetFaultReason ( EXCEPTION_POINTERS * pExPtrs )
{
  ....
  PIMAGEHLP_SYMBOL pSym = (PIMAGEHLP_SYMBOL)&g_stSymbol ;
  FillMemory ( pSym , NULL , SYM_BUFF_SIZE ) ;
  ....
}

このコードには、複数のバグがあります。少なくとも、第 2 引数と第 3 引数が混同されていることが分かります。そのため、アナライザーは 2 つの V575 (英語) 警告を出力しています。

  • V575 The ‘memset’ function processes value ‘512’. Inspect the second argument. crashhandler.cpp 499 (V575 ‘memset’ 関数が値 ‘512’ を処理しています。第 2 引数を確認してください。crashhandler.cpp 499)
  • V575 The ‘memset’ function processes ‘0’ elements. Inspect the third argument. crashhandler.cpp 499 (V575 ‘memset’ 関数が ‘0’ 要素を処理しています。第 3 引数を確認してください。crashhandler.cpp 499)

NULL は 0 であるため、コードはコンパイルされます。その結果、0 配列要素に値が格納されます。しかし、実際に問題となるのは、このエラーではありません。一般に、NULL はここでは不適切です。memset() 関数はバイトを操作するため、メモリーに NULL 値を格納することは無意味です。正しいコードは、次のようになります。

FillMemory(pSym, SYM_BUFF_SIZE, 0);

あるいは、次のようになります。

ZeroMemory(pSym, SYM_BUFF_SIZE);

しかし、これは重要ではありません。ここで重要なことは、この無意味なコードが問題なくコンパイルされるということです。もしプログラマーに NULL の代わりに nullptr を使用する習慣があれば、次のようなコードを記述したでしょう。

FillMemory(pSym, nullptr, SYM_BUFF_SIZE);

そして、コンパイラーがエラーメッセージを出力し、プログラマーは問題に気付き、コーディング方法にもっと注意を払うでしょう。

注: この場合、NULL を非難すべきでないことは理解しています。しかし、NULL のせいで不正なコードが警告なしにコンパイルされています。

推奨事項

今すぐ nullptr を使用し、コーディング基準を変更することを推奨します。

nullptr は愚かなエラーの回避に役立つため、開発プロセスをわずかにスピードアップします。

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

関連記事

  • Parallel Universe マガジンParallel Universe マガジン Parallel Universe へようこそ。 米国インテル社が四半期に一度オンラインで公開しているオンラインマガジンです。インテルの技術者によるテクノロジーの解説や、最新ツールの紹介など、並列化に関する記事を毎号掲載しています。第1号からのバックナンバーを PDF 形式で用意しました、ぜひご覧ください。 12 […]
  • 比較関数の罠比較関数の罠 この記事は、インテル® デベロッパー・ゾーンに公開されている「The Evil within the Comparison Functions」の日本語参考訳です。 この記事の PDF […]
  • インテル® IPP サンプル – エラーの修正インテル® IPP サンプル – エラーの修正 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Intel IPP Samples for Windows - error correction」の日本語参考訳です。 この記事は、PVS-Studio を使用することでプログラムがどのように安全になるかを説明した記事の 1 […]
  • ファクトシート: oneAPIファクトシート: oneAPI この記事は、インテル ニュースルームに公開されている「Fact Sheet: oneAPI」の日本語参考訳です。 この記事の PDF 版はこちらからご利用になれます。 oneAPI とは? oneAPI […]
  • 並列プログラミングにおけるロックの効率的な使用並列プログラミングにおけるロックの効率的な使用 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Locks Effectively in Parallel Programming […]