結論
一般にnullptrチェックのコストがかかると思われます.
なお,この記事では,アドレス値のコピーのコストについては議論しません.
詳細
C++において,ポインター間のstatic_castを行った場合,コンパイラーによって,必要なアドレスのオフセット値が算出され,その値をアドレスに加算するコードが生成されます.Cだとオフセット値を算出して加算するコードを明示的に書かないといけません(もちろん,ちょっとしたマクロを書けばタイプ量を減らすことは可能です).
C++のstatic_castはとても便利なのですが,仕様上nullptrはキャストしてもnullptrとなるため,単純にアドレスにオフセット値を加算するわけにはいきません.
例えば,以下のようなポインター間のstatic_castを行った場合,
// 単一継承の場合や,Bがメンバ変数を持っていない場合(多分コンパイラー依存)などは,
// static_castのnullptrチェックは発生しないと推測されるので,ここでは扱わない
class A { int a; };
class B { int b; };
class C : public A, public B {};
int main() {
C c;
B* b = static_cast<B*>(&c);
return 0;
}
以下のようなコードが生成されます.
$ clang++ -std=c++11 -S -emit-llvm test.cc # 最適化オブションなし
; Function Attrs: norecurse nounwind uwtable
define i32 @main() #0 {
%1 = alloca i32, align 4
%c = alloca %class.C, align 4
%b = alloca %class.B*, align 8
store i32 0, i32* %1, align 4
%2 = icmp eq %class.C* %c, null
br i1 %2, label %7, label %3
; <label>:3 ; preds = %0
%4 = bitcast %class.C* %c to i8*
%5 = getelementptr inbounds i8, i8* %4, i64 4
%6 = bitcast i8* %5 to %class.B*
br label %7
; <label>:7 ; preds = %3, %0
%8 = phi %class.B* [ %6, %3 ], [ null, %0 ]
store %class.B* %8, %class.B** %b, align 8
ret i32 0
}
アドレスに対してオフセット値を加算する前に,nullとの比較を行っていることがわかります.
最適化によって,確実にnullptrではない場合は,nullptrチェックを行わずアドレスにオフセット値を加算するコードを生成する場合があります.しかし,一般的にはnullptrかどうかは実行時に判定する必要があります(動的リンクされる共有ライブラリに含まれる関数・メソッドを呼び出す場合を考えるとよいかもしれません).そのため,仮に関数・メソッドの事後条件でnullptrでないことが保証されていたとしても,nullptrチェックがstatic_castの実行時コストとしてかかるものと考えられます.
あまり深く考えたわけではないため,間違っているかもしれませんが,多分static_castのnullptrチェックを強制的になくすようなコードを書くことはできないものと思います.もちろん,reinterpret_castを使い,オフセット値の加算を明示的に書けば,Cのようにnullptrチェックをなくせるでしょう.