この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。
25. ‘this’ を nullptr と比較しない
CoreCLR プロジェクトから抜粋した以下のコードについて考えてみます。この危険なコードは、次の PVS-Studio 診断によって検出されます。
V704 ‘this == nullptr’ expression should be avoided – this expression is always false on newer compilers, because ‘this’ pointer can never be NULL. (V704 ‘this == nullptr’ 式は避けるべきです。新しいコンパイラーでは ‘this’ ポインターが null になることはないため、この式は常に false になります。)
bool FieldSeqNode::IsFirstElemFieldSeq() { if (this == nullptr) return false; return m_fieldHnd == FieldSeqStore::FirstElemPseudoField; }
説明
以前は、this ポインターを 0/null/nullptr と比較していました。C++ が開発された当初は、これが一般的でした。このようなコードは、「考古学的」な操作を行っていることが分かりました。詳しくは、こちらの記事 (英語) をお読みください。さらに、当時は this ポインターの値を変更することができました。しかし、ずっと前のことなので、皆さんもすでに忘れていることでしょう。
this と nullptr の比較に戻りましょう。
現在、この操作は許可されていません。最近の C++ 標準では、this が nullptr と等しくなることはありません。
C++ 標準によると、null ポインター this に対する IsFirstElemFieldSeq() メソッドの呼び出しは、未定義の動作になります。
this==0 の場合、メソッドを実行している間、このクラスのフィールドにはアクセスできないように見えます。しかし実際には、このようなコードを実装する好ましくない方法が 2 つあります。C++ 標準によると、this ポインターが null になることはないため、コンパイラーは次のようにメソッド呼び出しを最適化できます。
bool FieldSeqNode::IsFirstElemFieldSeq() { return m_fieldHnd == FieldSeqStore::FirstElemPseudoField; }
もう 1 つ落とし穴があります。以下の継承階層があると仮定します。
class X: public Y, public FieldSeqNode { .... }; .... X * nullX = NULL; X->IsFirstElemFieldSeq();
Y クラスのサイズが 8 バイトの場合、ソースポインター NULL (0x00000000) は、FieldSeqNode サブオブジェクトの先頭を指すように変更されます。そのため、sizeof(Y) バイトにオフセットする必要があります。つまり、IsFirstElemFieldSeq() 関数で this は 0x00000008 となり、”this == 0″ チェックは全く無意味です。
正しいコード
このケースでは、正しいコード例を提供するのが非常に困難です。関数からこの条件を削除するだけでは十分ではありません。null ポインターを使用して関数を呼び出さないようにコードをリファクタリングする必要があります。
推奨事項
“if (this == nullptr)” は許可されなくなりました。しかし、まだ多くのアプリケーションやライブラリー (例えば、MFC ライブラリー) でこのコードを目にします。そのため、Visual C++* では引き続き this と 0 の比較に対応しています。コンパイラー開発者は、長年適切に動作してきたコードを変更することに積極的ではないのかもしれません。
しかし、最近の C++ 標準では許可されていないので、まずは this と null の比較を避けることから始めます。そして、時間のあるときに、既存のコードを確認して、すべての不正な比較を洗い出し、書き直すと良いでしょう。
おそらく、コンパイラーは今後次のように対応していくと考えられます。最初に、比較に関する警告を出力するでしょう。確認していないので分かりませんが、すでにこのような警告を出しているかもしれません。そしてある時点で、新しい標準を完全にサポートし、既存のコードは動作しなくなるでしょう。そのため、新しい標準に従うことを強く推奨します。そうすることで、後で助かるでしょう。
補足: リファクタリングする場合、null オブジェクト・パターン (英語) が必要になるかもしれません。
トピックの関連情報へのリンク:
- まだ “this” ポインターを null と比較していますか? (英語)
- 診断 V704 (英語)
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。