More C++ Idioms/nullptr
表示
nullptr
[編集]意図
[編集]整数の0とNULLポインタを区別するため。
別名
[編集]動機
[編集]C++では、長年にわたり、NULLポインタを指定するキーワードがないという恥ずかしさがありました。 C++11ではその恥ずかしさが解消されました。C++の強力な型チェックにより,CのNULLマクロは式の中ではほとんど役に立たなくなっています。
#define NULL ((void *)0) // 訳註:C++ では定義が異なる
std::string * str = NULL; // void * から std::string * へ自動的にキャストすることはできない
void (C::*pmf) () = &C::func;
if (pmf == NULL) {} // void * からメンバ関数ポインタへ自動的にキャストすることはできない
そのため、C++ では整数定数 0 をヌルポインタを指定するために使う。 これは圧倒的多数の場合で思惑どおり動作するが、曖昧なオーバーロード関数が存在する場合にはうまくいかない。 例えば、両方の関数が(オーバーロードの解決において)同程度に良い場合には、コンパイラは正しい関数を選択することができない。
void func(int);
void func (double *);
int main (void)
{
func (static_cast <double *>(0)); // 期待通り func(double *) が呼びだされる。
func (0); // func(int) が呼び出されるが、0 はヌルポインタでもあるため double * が望ましいのかもしれない。
func (NULL) // (訳註:NULL が上記の定義((void*)0) として)曖昧。double * と int は同程度に良く合致する。
}
解法とサンプルコード
[編集]nullptrイディオムは、ライブラリによるヌルポインタによって上記問題のいくつかを解決する。 Herb Sutter と Bjarne Stroustrup による最新のドラフト提案(N2431) では、新しいキーワード nullptr を C++ に追加することを推奨している。 nullptrイディオムは、現存する C++ の機能を使って、これに最も近い効果を得ようとするものである。 以下の nullptr の実装は、Scott Meyer によってその著書 Effective C++ 改訂2版内(25項)で示唆された、ライブラリベースのアプローチの変種である。
const // const オブジェクトであって、
class nullptr_t
{
public:
template<class T>
operator T*() const // 任意の非メンバ型のヌルポインタや、
{ return 0; }
template<class C, class T>
operator T C::*() const // 任意のメンバ型のヌルポインタに変換可能であって、
{ return 0; }
private:
void operator&() const; // アドレスを取得することができない。
} nullptr = {};
struct C
{
void func();
};
template<typename T>
void g( T* t ) {}
template<typename T>
void h( T t ) {}
void func (double *) {}
void func (int) {}
int main(void)
{
char * ch = nullptr; // ok
func (nullptr); // func(double *) が呼び出される
func (0); // func(int) が呼び出される
void (C::*pmf2)() = 0; // ok
void (C::*pmf)() = nullptr; // ok
nullptr_t n1, n2;
n1 = n2;
//nullptr_t *null = &n1; // アドレスを取得することはできない。
if (nullptr == ch) {} // ok
if (nullptr == pmf) {} // ok (VC8 では)
const int n = 0;
if (nullptr == n) {} // ok
//int p = 0;
//if (nullptr == p) {} // ok ではない
//g (nullptr); // T を導出することはできない
int expr;
char* ch3 = expr ? nullptr : nullptr; // ch1 はヌルポインタ値
//char* ch4 = expr ? 0 : nullptr; // エラー。比較不可能な型。
//int n3 = expr ? nullptr : nullptr; // エラー。nullptr は int に変換不可能。
//int n4 = expr ? 0 : nullptr; // エラー。比較不可能な型。
h( 0 ); // T = int として導出
h( nullptr ); // T = nullptr_t として導出
h( (float*) nullptr ); // T = float* として導出
sizeof( nullptr ); // ok
typeid( nullptr ); // ok
throw nullptr; // ok
}
不幸にも、gcc 4.1.1 コンパイラには nullptr とメンバ関数へのポインタ(pmf)との比較を認識することができないバグがあるようである。 上記コードは VC++ 8.0 でコンパイル可能である。
影響
[編集]この技法には、 提案 N2431 で論じられているように、いくつかの欠点がある。 その欠点は以下のように要約できる。
- nullptrイディオムを使うためにヘッダをインクルードしなくてはならない。これにより、言語がヌルポインタに対して組み込みのキーワードを持っていないということがはっきりしてしまう。
- nullptrがライブラリとして実装される場合、コンパイラは意味のあるエラーメッセージを出力することができない。