nullptrとは正確には何ですか?


570

これで、C ++ 11に多くの新機能が追加されました。(少なくとも私にとって)面白くて混乱するのは、新しいnullptrです。

まあ、厄介なマクロはもう必要ありませんNULL

int* x = nullptr;
myclass* obj = nullptr;

それでも、nullptr動作がわかりません。たとえば、Wikipediaの記事では次のように述べています。

C ++ 11では、これを修正するために、識別されたnullポインター定数として機能する新しいキーワード nullptrを導入しています。これはnullptr_t型であり、暗黙的に変換可能であり、任意のポインター型またはメンバーへのポインター型と比較できます。boolを除いて、暗黙的に変換したり、整数型と比較したりすることはできません。

キーワードと型のインスタンスはどうですか?

また、nullptr古き良きより優れている別の例(ウィキペディア以外のもの)はあり0ますか?


23
関連ファクト:nullptrC ++ / CLIのマネージハンドルのnull参照を表すためにも使用されます。
Mehrdad Afshari、

3
Visual C ++を使用する場合、nullptrをネイティブC / C ++コードで使用し、/ clrコンパイラーオプションでコンパイルすると、nullptrがネイティブまたはマネージのnullポインター値を示すかどうかをコンパイラーが判断できないことに注意してください。コンパイラーに意図を明確にするには、nullptrを使用して管理対象の値を指定するか、__ nullptrを使用してネイティブの値を指定します。Microsoftはこれをコンポーネント拡張として実装しました。
cseder 2013年

6
されnullptr_t、唯一つのメンバーを持つことが保証nullptr?したがって、関数がを返した場合、関数nullptr_tの本文に関係なく、コンパイラはどの値が返されるかをすでに知っていますか?
アーロン・マクデイド、2015

8
@AaronMcDaid std::nullptr_tはインスタンス化できますがnullptr、型がとして定義されているため、すべてのインスタンスはと同じになりますtypedef decltype(nullptr) nullptr_t。この型が存在する主な理由はnullptr、必要に応じて、関数を特にオーバーロードしてcatchできるようにするためです。例については、こちらをご覧ください。
ジャスティンタイム-モニカ

5
0はヌルポインタがで取得することができポインタで、NULLポインタだったことはありませんキャスト ポインタ型にゼロリテラルを、それが指していない任意の定義によって既存のオブジェクト。
スウィフト-フライデーパイ

回答:


403

キーワードと型のインスタンスはどうですか?

これは驚くべきことではありません。どちらtruefalseキーワードであり、リテラルとして、彼らは(タイプを持っていますbool)。nullptrあるポインタリテラルタイプのはstd::nullptr_t、それがprvalue(あなたはそれが使用のアドレスを取ることができないのです&)。

  • 4.10ポインタ変換については、typeのprvalueはstd::nullptr_tnullポインタ定数であり、整数のnullポインタ定数はに変換できると述べていstd::nullptr_tます。反対方向は許可されていません。これにより、ポインターと整数の両方の関数をオーバーロードnullptrし、ポインターのバージョンを選択するために渡すことができます。合格NULLまたは0混乱を招くintバージョンの選択。

  • nullptr_t整数型へのキャストにはが必要でありreinterpret_cast、整数型へのキャストと同じセマンティクスを持っています(void*)0(マッピング実装が定義されています)。はポインタ型にreinterpret_cast変換できませんnullptr_t。可能な場合は暗黙的な変換に依存するか、を使用しますstatic_cast

  • 標準ではその必要がsizeof(nullptr_t)BEをsizeof(void*)


ああ、調べたところ、のような場合、条件演算子は0をnullptrに変換できないようcond ? nullptr : 0;です。私の回答から削除されました。
ヨハネスシャウブ-litb 2009

88
NULLあることさえ保証されないことに注意してください0。可能性があります0L。その場合、への呼び出しvoid f(int); void f(char *);はあいまいになります。nullptr常にポインタのバージョンを優先し、それを呼び出すことはありませんint。また、これnullptr はに変換可能であることに注意してくださいbool(ドラフトではでそれを述べています4.12)。
Johannes Schaub-litb

@litb:f(int)とf(void *)に関して-f(0)はまだあいまいですか?
スティーブフォリー

27
@Steve、いいえ、それはintバージョンを呼び出します。しかしf(0L)ので、あいまいですlong -> intaswellとしてlong -> void*両方均等にコストがかかります。したがって0L、コンパイラにNULLがある場合、f(NULL)これらの2つの関数を指定すると、呼び出しはあいまいになります。もちろんそうでnullptrはありません。
ヨハネスシャウブ-litb 2009

2
@SvenS (void*)0C ++のように定義しないでください。しかし、任意のnullポインター定数として定義でき、値0の任意の整数定数がnullptr満たされます。だから、間違いなくそうするつもりはありませんできます。(あなたは私にpingするのを忘れました...)
Deduplicator

60

nullptrから:タイプセーフでクリアカットのヌルポインター

新しいC ++ 09 nullptrキーワードは、バグのある弱く型付けされたリテラル0と悪名高いNULLマクロを置き換える、汎用nullポインターリテラルとして機能する右辺値定数を指定します。したがって、nullptrは、30年を超える恥ずかしさ、あいまいさ、およびバグに終止符を打ちます。次のセクションでは、nullptr機能を紹介し、NULLと0の病気をどのように治療できるかを示します。

その他の参考資料:


17
C ++ 09?2011年8月以前はC ++ 0xと呼ばれていませんでしたか?
Michael Dorst 2013年

2
@anthropomorphicまあ、それが目的です。C ++ 0xは、2008年と2009年のどちらで終了するのかがわからなかったため、進行中の作業中に使用されました。実際には、C ++ 0BがC ++ 11を意味することに注意してください。stroustrup.com/C++11FAQ.htmlを
mxmlnkn

44

C ++ 11でnullptrを使用する理由 それは何ですか?NULLが十分でないのはなぜですか?

C ++のエキスパートであるAlex Allainは、ここでそれを完全に述べています(私の強調は太字で追加されています)。

...次の2つの関数宣言があるとします。

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

2番目の関数が呼び出されるように見えますが(結局のところ、ポインターのように見えるものを渡しています)、これが実際に呼び出される最初の関数です!問題は、NULLが0で、0が整数であるため、代わりにfuncの最初のバージョンが呼び出されることです。これは、はい、常に発生するわけではありませんが、発生した場合、非常にイライラし、混乱します。何が起こっているかの詳細がわからない場合は、コンパイラのバグのように見えるかもしれません。コンパイラのバグのように見える言語機能は、まあ、あなたが望むものではありません。

nullptrを入力します。C ++ 11では、nullptrは、NULLポインターを表すために使用できる(そして使用すべきです!)新しいキーワードです。つまり、以前にNULLを書き込んでいた場合は、代わりにnullptrを使用する必要があります。プログラマーにとって、これはもはや明確ではありません(NULLの意味を誰もが知っています)、コンパイラーにとってはより明確であり、ポインターとして使用されるときに、どこにも使用されている0が特別な意味を持つことはもうありません。

Allainは彼の記事を次のように終わらせます:

これらすべてに関係なく、C ++ 11の経験則は、以前はnullptr使用NULLしていなかった場合に使用を開始することです。

(私の言葉):

最後に、それnullptrがオブジェクト、つまりクラスであることを忘れないでください。それはどこでも使用することができますNULL前に使用されましたが、あなたには、いくつかの理由で、その型が必要な場合は、それのタイプを用いて抽出することができdecltype(nullptr)、または直接として記述std::nullptr_tするだけである、typedefdecltype(nullptr)

参照:

  1. Cprogramming.com:C ++ 11のより良い型-nullptr、列挙型クラス(強く型付けされた列挙型)、およびcstdint
  2. https://en.cppreference.com/w/cpp/language/decltype
  3. https://en.cppreference.com/w/cpp/types/nullptr_t

2
私はあなたの答えが過小評価されていると言わなければなりません、あなたの例を通して理解することは非常に簡単でした。
mss

37

複数の型へのポインタを受け取ることができる関数がある場合、それを使って呼び出すことNULLはあいまいです。これが現在回避されている方法は、intを受け入れ、それがであると想定することにより、非常にハックNULLです。

template <class T>
class ptr {
    T* p_;
    public:
        ptr(T* p) : p_(p) {}

        template <class U>
        ptr(U* u) : p_(dynamic_cast<T*>(u)) { }

        // Without this ptr<T> p(NULL) would be ambiguous
        ptr(int null) : p_(NULL)  { assert(null == NULL); }
};

C++11あなたにオーバーロードすることができるだろうnullptr_tことはのでptr<T> p(42);、コンパイル時エラーではなく、実行時になりますassert

ptr(std::nullptr_t) : p_(nullptr)  {  }

NULLとして定義されている場合はどうなり0Lますか?
LF

9

nullptrのような整数型に割り当てることはできませんがint、ポインタ型のみに割り当てることができます。などの組み込みポインタ型int *ptrまたはなどのスマートポインタstd::shared_ptr<T>

これは重要な違いだと思います。これは、NULL整数型とポインターの両方に割り当てることができNULL、マクロが展開されて、ポインターの0初期値とintポインターの両方として機能できるためです。


この答えは間違っていることに注意してください。 NULLへの展開は保証されていません0
LF

6

また、nullptr古き良き0よりも優れている別の例(ウィキペディア以外のもの)はありますか?

はい。これは、実稼働コードで発生した(簡略化された)実世界の例でもあります。レジスタ幅が異なるプラットフォームにクロスコンパイルするときにgccが警告を出すことができたので、それは際立っていました(まだx86_64からx86にクロスコンパイルするときだけ、なぜかは正確にはわかりませんが、警告します)warning: converting to non-pointer type 'int' from NULL)。

次のコードを検討してください(C ++ 03):

#include <iostream>

struct B {};

struct A
{
    operator B*() {return 0;}
    operator bool() {return true;}
};

int main()
{
    A a;
    B* pb = 0;
    typedef void* null_ptr_t;
    null_ptr_t null = 0;

    std::cout << "(a == pb): " << (a == pb) << std::endl;
    std::cout << "(a == 0): " << (a == 0) << std::endl; // no warning
    std::cout << "(a == NULL): " << (a == NULL) << std::endl; // warns sometimes
    std::cout << "(a == null): " << (a == null) << std::endl;
}

次の出力が得られます。

(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1

nullptr(およびC ++ 11)を使用すると、これがどのように改善されるかはわかりません。pbをnullptrに設定した場合、最初の比較はまだ真と評価されます(リンゴを梨と比較している間...)。2番目のケースはさらに悪いです。aをnullptrと比較すると、aがB *に変換され、(trueに評価される前に(ブール値にキャストされ、exprがfalseに評価される前))。全体的に私はJavaScriptを思い出し、将来C ++で===を取得するかどうか疑問に思います:(
Nils

5

さて、他の言語には型のインスタンスである予約語があります。たとえば、Python:

>>> None = 5
  File "<stdin>", line 1
SyntaxError: assignment to None
>>> type(None)
<type 'NoneType'>

これはNone通常、初期化されていないものに使用されるため、実際にはかなり近い比較ですが、同時にNone == 0false などの比較が行われます。

一方、プレーンCでは、 NULL == 0真のIIRCを返しますNULL。これは、0を返すマクロなので、常に無効なアドレス(AFAIK)だからです。


4
NULLゼロに展開するマクロです。ポインターに定数ゼロをキャストすると、nullポインターが生成されます。nullポインターはゼロである必要はありません(ただし、多くの場合ゼロです)。ゼロは常に無効なアドレスであるとは限りません。また、ポインターへの非定数ゼロキャストはnullである必要はありません。整数はゼロである必要はありません。何もかも忘れずに大丈夫だったかな。参照:c-faq.com/null/null2.html
Samuel Edwin Ward

3

規格で指定されているため、キーワードです。;-)最新の公開草案(n2914)による

2.14.7ポインタリテラル[lex.nullptr]

pointer-literal:
nullptr

ポインタリテラルはキーワードnullptrです。タイプの右辺値ですstd::nullptr_t

暗黙的に整数値に変換しないので便利です。


2

intとchar *の両方を受け取るようにオーバーロードされた関数(f)があるとします。C ++ 11より前では、nullポインターを使用してそれを呼び出す必要があり、NULL(つまり、値0)を使用した場合、intに対してオーバーロードされたものを呼び出します。

void f(int);
void f(char*);

void g() 
{
  f(0); // Calls f(int).
  f(NULL); // Equals to f(0). Calls f(int).
}

これはおそらくあなたが望んでいたものではありません。C ++ 11はこれをnullptrで解決します。これで、次のように記述できます。

void g()
{
  f(nullptr); //calls f(char*)
}

1

まず、洗練されていない実装を紹介しましょう nullptr_t

struct nullptr_t 
{
    void operator&() const = delete;  // Can't take address of nullptr

    template<class T>
    inline operator T*() const { return 0; }

    template<class C, class T>
    inline operator T C::*() const { return 0; }
};

nullptr_t nullptr;

nullptrは、割り当て先のインスタンスのタイプに応じて、正しいタイプのnullポインターを自動的に推定するReturn Type Resolverイディオムの微妙な例です。

int *ptr = nullptr;                // OK
void (C::*method_ptr)() = nullptr; // OK
  • 上記のように、nullptrが整数ポインターに割り当てられるintと、テンプレート化された変換関数の型のインスタンス化が作成されます。メソッドポインタにも同じことが言えます。
  • このように、テンプレートの機能を活用することで、実際に毎回適切なタイプのnullポインターを作成し、新しいタイプの割り当てを行います。
  • nullptrゼロの値を持つ整数リテラルである、あなたはできません我々は削除&オペレータによって達成そのアドレスを使用することができます。

nullptrそもそもなぜ必要なのでしょうか。

  • NULL以下のように、従来型にはいくつかの問題があることがわかります。

1️⃣暗黙の変換

char *str = NULL; // Implicit conversion from void * to char *
int i = NULL;     // OK, but `i` is not pointer type

2️⃣関数呼び出しのあいまいさ

void func(int) {}
void func(int*){}
void func(bool){}

func(NULL);     // Which one to call?
  • コンパイルすると次のエラーが発生します。
error: call to 'func' is ambiguous
    func(NULL);
    ^~~~
note: candidate function void func(bool){}
                              ^
note: candidate function void func(int*){}
                              ^
note: candidate function void func(int){}
                              ^
1 error generated.
compiler exit status 1

3️⃣コンストラクターのオーバーロード

struct String
{
    String(uint32_t)    {   /* size of string */    }
    String(const char*) {       /* string */        }
};

String s1( NULL );
String s2( 5 );
  • そのような場合、明示的なキャストが必要です(つまり、  String s((char*)0))

0

0は、ポインターのキャストフリー初期化子として使用できる唯一の整数値でした。キャストなしで他の整数値でポインターを初期化することはできません。0は、構文的に整数リテラルに類似したconsexprシングルトンと見なすことができます。任意のポインタまたは整数を開始できます。しかし、驚くべきことに、明確な型がないことがわかります。これは、ポインターを初期化するためのnull値の実際のシングルトンconstexpr表現であることが提案されたものです。整数を直接初期化するために使用することはできず定義と定義する曖昧さを排除int。です。では、どうして0はポインタを初期化でき、1は初期化できないのでしょうか。実用的な答えは、ポインターのnull値を定義する手段が必要であり、ポインターへの直接の暗黙的な変換intはエラーが発生しやすいことでした。したがって、0は先史時代から本物の変な奇妙な獣になりました。 nullptrNULL、0にするnullptrstd構文を使用してライブラリとして定義することもできますが、意味的にはコアコンポーネントがないように見えました。 一部のライブラリがそれをとして定義することを決定しない限りNULL、は現在ではに代わり非推奨とnullptrなっていnullptrます。


-1

これがLLVMヘッダーです。

// -*- C++ -*-
//===--------------------------- __nullptr --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_NULLPTR
#define _LIBCPP_NULLPTR

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#ifdef _LIBCPP_HAS_NO_NULLPTR

_LIBCPP_BEGIN_NAMESPACE_STD

struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
    void* __lx;

    struct __nat {int __for_bool_;};

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}

    template <class _Tp>
        _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
        operator _Tp* () const {return 0;}

    template <class _Tp, class _Up>
        _LIBCPP_INLINE_VISIBILITY
        operator _Tp _Up::* () const {return 0;}

    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};

inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}

#define nullptr _VSTD::__get_nullptr_t()

_LIBCPP_END_NAMESPACE_STD

#else  // _LIBCPP_HAS_NO_NULLPTR

namespace std
{
    typedef decltype(nullptr) nullptr_t;
}

#endif  // _LIBCPP_HAS_NO_NULLPTR

#endif  // _LIBCPP_NULLPTR

(すぐに多くのことが明らかになりますgrep -r /usr/include/*`

飛び出すのは、演算子の*オーバーロードです(0を返すことは、segfaultingよりもずっと親切です...)。もう一つは、それがアドレスを記憶するとの互換性を見ていないで、すべてに。これは、void *を投げてNULLの結果を通常のポインターにセンチネル値として渡す方法と比較して、「決して忘れない、爆弾である可能性がある」要因を明らかに減らすでしょう。


-2

NULLは0である必要はありません。常にNULLを使用し、0を使用しない限り、NULLは任意の値にすることができます。フォンノイマンマイクロコントローラーをフラットメモリでプログラムすると仮定すると、その割り込みベクトルは0です。NULLが0で、NULLポインターで何かが書き込まれると、マイクロコントローラーがクラッシュします。NULLが1024で、1024に予約された変数がある場合、書き込みはクラッシュせず、プログラム内からNULLポインターの割り当てを検出できます。これはPCでは無意味ですが、宇宙探査機、軍事または医療機器の場合、クラッシュしないことが重要です。


2
まあ、メモリ内のnullポインターの実際の値はゼロではないかもしれませんが、C(およびC ++)標準では、コンパイラーに整数0リテラルをnullポインターに変換するように指示しています。
bzim 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.