メソッド内でのthis最適化

順調に動いていたはずのプログラムが突然ダンプを吐いてクラッシュする・・・。

まさに悲劇としか言えません。

ある調査機関によると成人男性の約89%がこの不幸に出会っているという報告もあります。[要出典]

最も多く、そして最も簡単に発生する原因としてnullポインタアクセスが挙げられます。以下のコードを見てみましょう。

このプログラムは実行するとすぐさまクラッシュします。当然ですね。

問題はどこでアクセスエラーが発生するか?です。

正解は5行目の

の部分なのですが、なぜメソッド呼び出し

ではなく、その内部の処理まで進むのでしょうか。

理由はc++のアーキテクチャにあります。

あるクラスのメソッド、あるインスタンスのメソッド呼び出しとは、内部的には通常の関数にインスタンスのポインタを渡しているだけなのです。暗黙の引数としてthisが渡されるイメージです。

ですので、先ほど提示したコードは、C言語的な擬似コードで書くと

というコードに近いイメージになります。

これならアクセスエラーが発生する場所が

だという事がよりイメージしやすいのではないかと思います。

C++でコーディングしていると、ポインタを操作する前にnullチェックをするコードを書くケースが多いのですが、上記を踏まえ、こんなことを考えるかもしれません。

 

そうだ!メソッドの中でチェックしよう!

 

 

と書くかわりに

こんな感じでメソッドの中でチェックすれば、呼び出し側が、色々な場所で呼び出すかわりにnullチェックしなくて済む。と思うかもしれません。

 

 

しかし、このコードは再びアクセスエラーになる可能性があります。

なぜか?

一見、ちゃんとnullチェックもしているように見えます。

 

C++の規格ではメソッド中でthisがnullではないことが保証されているため優秀なコンパイラは単にメソッド内でのthisへのnull判定コードを最適化で消し去ります。

これが今回のアーティクルのタイトルにもなっているメソッド内でのthisの最適化です。

コンパイラオプションによってこの最適化を抑制することもできるのですが、規格に準じたコードではないので書くべきではありません。

 

コラム

行儀よく普通にコーディングしてるとメソッドの中でthisを判定したいことは少ないと思います。こんな最適化に関する知識、それほど重要じゃないと思われるかもしれません。

あるゲームエンジンを書いていた時のことです。

そのゲームエンジンはオブジェクトを生成すると同時に色々な準備を同時に行う必要があったのでファクトリによって生成されました。生成と破棄の詳細についてユーザは知る必要がないのでインスタンスを破棄する時、ユーザーは単に破棄用のメソッドを呼びます。

不特定多数のゲームプログラマが使うようなシステムを実装する場合、ゲームエンジンを書いているプログラマは「とにかく簡単に完結に」 という心理になります。

delete演算子にnullポインタを渡しても支障が無いので、この破棄処理もobjがnullかどうか破棄の前に判定させることを強要しない作りにしたほうがシンプルで良いと考えるのです。

そして……メソッドの中でthisによる判定を書いてしまうことが……あるのです……。

あなたがもし、ゲームエンジンの実装を任されているエンジニアで、なおかつdeleteがnullポインタを許容する事実を知っている場合は、この最適化の事を覚えておいて損はないでしょう。

そして、もしよりシンプルでブレの無いエンジンを目指すなら、そもそもオブジェクトの生成はスマートポインタで返すべきかもしれません。(ハードスペックと使っているコンパイラが許すなら)

 

 

まとめ

  1. メソッド中ではthisがnullじゃないことは規格で保証されている
  2. 故にメソッド中でのthisそのものに対する判定は最適化される場合がある
  3. コンパイラオプションで抑制できるとしてもメソッド内でthis判定するのは規格外なので良くない

 

Pocket