More C++ Idioms/nullptr

出典: フリー教科書『ウィキブックス(Wikibooks)』
移動: 案内検索

nullptr[編集]

意図[編集]

整数の 0 とヌルポインタを区別する。

別名[編集]

動機[編集]

何年もの間、C++ にはヌルポインタを指すキーワードがないという決まり悪さが存在した。 最新の標準である C++0x (訳註:現在標準化委員会で検討中)では、この決まり悪さが取り除かれることが約束されている。 C++ では C の NULL マクロを用いることができない(訳註:C++ では (void *)0 は NULL の定義として認められていない)。 なぜなら、下記のような式では、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がライブラリとして実装される場合、コンパイラは意味のあるエラーメッセージを出力することができない。

既知の利用[編集]

関連するイディオム[編集]

References[編集]