170

このコードは、概念的には 3 つのポインターに対して同じことを行います (安全なポインターの初期化)。

int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;

nullptrでは、ポインタに値を割り当てるよりも、ポインタを割り当てる利点は何NULLですか0?

4

7 に答える 7

190

そのコードでは、利点はないようです。ただし、次のオーバーロードされた関数を検討してください。

void f(char const *ptr);
void f(int v);

f(NULL);  //which function will be called?

どの関数が呼び出されますか? もちろん、ここでの意図は を呼び出すことですf(char const *)が、実際f(int)には が呼び出されます! それは大きな問題です1ですね。

したがって、このような問題の解決策は次を使用することnullptrです。

f(nullptr); //first function is called

もちろん、それだけが の利点ではありませんnullptr。ここに別のものがあります:

template<typename T, T *ptr>
struct something{};                     //primary template

template<>
struct something<nullptr_t, nullptr>{};  //partial specialization for nullptr

テンプレートでは、 の型はnullptrと推定されるnullptr_tため、次のように記述できます。

template<typename T>
void f(T *ptr);   //function to handle non-nullptr argument

void f(nullptr_t); //an overload to handle nullptr argument!!!

1. C++ では、NULLは として定義されて#define NULL 0いるため、基本的intには です。それが と呼ばれる理由f(int)です。

于 2012-12-11T08:39:27.020 に答える
90

C++11 では、ポインター定数とnullptr呼ばれる が導入され、型の安全性が向上し、既存の実装依存の null ポインター定数とは異なり、あいまいな状況が解決されます。の利点を理解できるようになる。まず、何が問題で、何が問題なのかを理解する必要があります。NullNULLnullptrNULL


NULL正確には何ですか?

C++11 以前NULLは、値を持たないポインター、または有効なものを指していないポインターを表すために使用されていました。一般的な概念に反して、NULLは C++ のキーワードではありません。標準ライブラリのヘッダーで定義されている識別子です。NULLつまり、いくつかの標準ライブラリ ヘッダーをインクルードしないと使用できません。サンプル プログラムを考えてみましょう:

int main()
{ 
    int *ptr = NULL;
    return 0;
}

出力:

prog.cpp: In function 'int main()':
prog.cpp:3:16: error: 'NULL' was not declared in this scope

C++ 標準では、特定の標準ライブラリ ヘッダー ファイルで定義された実装定義マクロとして NULL を定義しています。NULL の起源は C にあり、C++ は C から継承しました。C 標準では NULL を0orとして定義しまし(void *)0た。しかし、C++ には微妙な違いがあります。

C++ はこの仕様をそのまま受け入れることができませんでした。C とは異なり、C++ は厳密に型指定された言語です (C++ では明示的なキャストが必要ですが、void*C では型への明示的なキャストは必要ありません)。これにより、C 標準で指定された NULL の定義が、多くの C++ 式で役に立たなくなります。例えば:

std::string * str = NULL;         //Case 1
void (A::*ptrFunc) () = &A::doSomething;
if (ptrFunc == NULL) {}           //Case 2

NULL が として定義されている場合(void *)0、上記の式はどちらも機能しません。

  • ケース 1:void *からへの自動キャストが必要なため、コンパイルされませんstd::string
  • ケース 2:void *メンバー関数へのポインターへの キャストが必要なため、コンパイルされません。

そのため、C とは異なり、C++ 標準では NULL を数値リテラル0orとして定義することが義務付けられています0L


では、すでにヌル ポインター定数を持っているのに、別のヌル ポインター定数が必要になるのは何NULLでしょうか?

C++ 標準委員会は C++ で機能する NULL 定義を考え出しましたが、この定義にはかなりの問題がありました。NULL は、ほとんどすべてのシナリオで十分に機能しましたが、すべてではありませんでした。特定のまれなシナリオで、驚くべき誤った結果が得られました。:

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    doSomething(NULL);
    return 0;
}

出力:

In Int version

char*明らかに、引数として取るバージョンを呼び出すことが意図されているようですが、出力が示すように、intバージョンを取る関数が呼び出されます。これは、NULL が数値リテラルであるためです。

さらに、NULL が 0 か 0L かは実装定義であるため、関数のオーバーロードの解決に多くの混乱が生じる可能性があります。

サンプル プログラム:

#include <cstddef>

void doSomething(int);
void doSomething(char *);

int main()
{
  doSomething(static_cast <char *>(0));    // Case 1
  doSomething(0);                          // Case 2
  doSomething(NULL)                        // Case 3
}

上記のスニペットを分析すると:

  • ケース 1:期待どおりに呼び出しますdoSomething(char *)
  • ケース 2:呼び出しますdoSomething(int)が、 IS もヌル ポインターである char*ため、バージョンが必要な場合があります。0
  • ケース 3:NULLが として定義されている場合、おそらく意図したとき0に呼び出し、実行時に論理エラーが発生する可能性があります。が として定義されている場合、呼び出しがあいまいで、コンパイル エラーが発生します。doSomething(int)doSomething(char *)NULL0L

したがって、実装によっては、同じコードがさまざまな結果をもたらす可能性があり、これは明らかに望ましくありません。当然のことながら、C++ 標準委員会はこれを修正したいと考えており、それが nullptr の主な動機です。


では、それは何でありnullptr、どのように問題を回避するのNULLでしょうか?

nullptrC++11 では、 null ポインター定数として機能する新しいキーワードが導入されています。NULL とは異なり、その動作は実装定義ではありません。これはマクロではありませんが、独自のタイプを持っています。nullptr の型はstd::nullptr_tです。C++11 は、NULL の欠点を回避するために、nullptr のプロパティを適切に定義します。その特性を要約すると、次のようになります。

プロパティ 1:独自の型std::nullptr_tを持ち、
プロパティ 2:暗黙的に変換可能であり、任意のポインター型またはメンバーへのポインター型に匹敵しますが、
プロパティ 3:を除いて、暗黙的に変換可能または整数型に匹敵しませんbool

次の例を検討してください。

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    char *pc = nullptr;      // Case 1
    int i = nullptr;         // Case 2
    bool flag = nullptr;     // Case 3

    doSomething(nullptr);    // Case 4
    return 0;
}

上記のプログラムでは、

  • ケース 1: OK - プロパティ 2
  • ケース 2:問題あり - プロパティ 3
  • ケース 3: OK - プロパティ 3
  • ケース 4:混乱なし -char *バージョン、プロパティ 2 と 3 を呼び出す

したがって、nullptr の導入により、古き良き NULL のすべての問題が回避されます。

どのように、どこで使用する必要がありますnullptrか?

C++11 の経験則はnullptr、過去に NULL を使用していた場合はいつでも使用を開始することです。


標準参照:

C++11 標準: C.3.2.4 マクロ NULL
C++11 標準: 18.2 型
C++11 標準: 4.10 ポインター変換
C99 標準: 6.3.2.3 ポインター

于 2013-01-01T16:18:58.923 に答える
24

ここでの本当の動機は完全転送です。

検討:

void f(int* p);
template<typename T> void forward(T&& t) {
    f(std::forward<T>(t));
}
int main() {
    forward(0); // FAIL
}

簡単に言えば、 0 は特別な値ですが、値はシステムを介して伝播できません。タイプのみが伝播できます。転送機能は必須であり、0 では処理できません。したがって、タイプが特別なものであり、タイプが実際に伝播できるnullptr場合、を導入することが絶対に必要でした。実際、MSVC チームは右辺値参照を実装した後、スケジュールよりも早く導入する必要があり、その後、自分自身でこの落とし穴を発見しました。nullptr

nullptr人生を楽にすることができるいくつかのコーナーケースがありますが、キャストがこれらの問題を解決できるため、コアケースではありません. 検討

void f(int);
void f(int*);
int main() { f(0); f(nullptr); }

2 つの個別のオーバーロードを呼び出します。さらに、

void f(int*);
void f(long*);
int main() { f(0); }

これはあいまいです。ただし、nullptr を使用すると、

void f(std::nullptr_t)
int main() { f(nullptr); }
于 2012-12-11T13:48:45.940 に答える
5

nullptr の基本

std::nullptr_tnull ポインタリテラル nullptr の型です。タイプの prvalue/rvaluestd::nullptr_tです。nullptr から任意のポインター型の null ポインター値への暗黙的な変換が存在します。

リテラル 0 は int であり、ポインターではありません。ポインターしか使用できないコンテキストで C++ が 0 を参照していることに気付いた場合、C++ はしぶしぶ 0 をヌル ポインターとして解釈しますが、これはフォールバック位置です。C++ の主なポリシーは、0 はポインタではなく int であるということです。

利点 1 - ポインターおよび整数型をオーバーロードするときのあいまいさを取り除く

C++98 では、このことの主な意味は、ポインターと整数型のオーバーロードが驚くべきことにつながる可能性があるということでした。このようなオーバーロードに 0 または NULL を渡しても、ポインター オーバーロードは呼び出されません。

   void fun(int); // two overloads of fun
    void fun(void*);
    fun(0); // calls f(int), not fun(void*)
    fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*)

この呼び出しの興味深い点は、ソース コードの見かけ上の意味 (「null ポインターで fun を呼び出しています」) と実際の意味 (「null ではなく何らかの整数で fun を呼び出しています」) との間の矛盾です。ポインタ」)。

nullptr の利点は、整数型がないことです。オーバーロードされた関数 fun を nullptr で呼び出すと、void* オーバーロード (つまり、ポインター オーバーロード) が呼び出されます。

fun(nullptr); // calls fun(void*) overload 

したがって、0 または NULL の代わりに nullptr を使用すると、オーバーロード解決の驚きを回避できます。

戻り値の型に auto を使用する場合のnullptroverのもう 1 つの利点NULL(0)

たとえば、コードベースでこれに遭遇したとします。

auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}

findRecord が何を返すかをたまたま知らない (または簡単に見つけられない) 場合、結果がポインター型なのか整数型なのかはっきりしない可能性があります。結局のところ、0 (テスト対象の結果) はどちらの方向にも進む可能性があります。一方、次のように表示される場合は、

auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}

あいまいさはありません。結果はポインター型でなければなりません。

メリット3

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

void lockAndCallF1()
{
        MuxtexGuard g(f1m); // lock mutex for f1
        auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
        cout<< result<<endl;
}

void lockAndCallF2()
{
        MuxtexGuard g(f2m); // lock mutex for f2
        auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
        cout<< result<<endl;
}
void lockAndCallF3()
{
        MuxtexGuard g(f3m); // lock mutex for f2
        auto result = f3(nullptr);// pass nullptr as null ptr to f3 
        cout<< result<<endl;
} // unlock mutex
int main()
{
        lockAndCallF1();
        lockAndCallF2();
        lockAndCallF3();
        return 0;
}

上記のプログラムは正常にコンパイルおよび実行されましたが、lockAndCallF1、lockAndCallF2、lockAndCallF3 には冗長なコードがあります。これらすべてのテンプレートを記述できるのであれば、このようなコードを記述するのは残念lockAndCallF1, lockAndCallF2 & lockAndCallF3です。したがって、テンプレートを使用して一般化できます。冗長コード lockAndCallの複数定義の代わりにテンプレート関数を記述しました。lockAndCallF1, lockAndCallF2 & lockAndCallF3

コードは次のようにリファクタリングされます。

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
//decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
        MuxtexGuard g(mutex);
        return func(ptr);
}
int main()
{
        auto result1 = lockAndCall(f1, f1m, 0); //compilation failed 
        //do something
        auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed
        //do something
        auto result3 = lockAndCall(f3, f3m, nullptr);
        //do something
        return 0;
}

lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)コンパイルが失敗した理由の詳細分析lockAndCall(f3, f3m, nullptr)

のコンパイルがlockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)失敗したのはなぜですか?

問題は、0 が lockAndCall に渡されると、その型を把握するためにテンプレートの型推定が開始されることです。0 の型は int であるため、lockAndCall へのこの呼び出しのインスタンス化内のパラメーター ptr の型です。残念ながら、これは lockAndCall 内の func への呼び出しで int が渡されていることを意味し、それは予期されるstd::shared_ptr<int>パラメーターと互換性がありませんf1。への呼び出しで渡された 0 はlockAndCall、null ポインターを表すことを意図していましたが、実際に渡されたのは int でした。この int を a として f1 に渡そうとするstd::shared_ptr<int>と、型エラーになります。with 0の呼び出しはlockAndCall失敗します。これは、テンプレート内で int がstd::shared_ptr<int>.

関連する呼び出しの分析は、NULL本質的に同じです。がNULLに渡されるlockAndCallと、パラメーター ptr に対して整数型が推定され、ptrint または int に似た型が に渡されると型エラーが発生f2しますstd::unique_ptr<int>

対照的に、関与する呼び出しnullptrは問題ありません。がnullptrに渡されるとlockAndCall、 の型は でptrあると推定されますstd::nullptr_t。がptrに渡されると、すべてのポインター型に暗黙的に変換されるため、f3からstd::nullptr_tへの暗黙的な変換が行われます。int*std::nullptr_t

null ポインターを参照する場合は、0 や ではなく nullptr を使用することをお勧めしますNULL

于 2015-12-28T15:44:06.077 に答える
4

他の人がすでに言ったように、その主な利点はオーバーロードにあります。また、明示的なintオーバーロードとポインターのオーバーロードはまれな場合がありますが、次のような標準ライブラリ関数を検討std::fillしてください (これは C++03 で何度も噛みつきました)。

MyClass *arr[4];
std::fill_n(arr, 4, NULL);

コンパイルしません: Cannot convert int to MyClass*.

于 2012-12-11T08:52:33.127 に答える
4

例を示した方法で持つことの直接的な利点はありませんnullptr
しかし、同じ名前の関数が 2 つある状況を考えてみましょう。1 テイクintともう1 テイクint*

void foo(int);
void foo(int*);

foo(int*)NULL を渡して呼び出したい場合の方法は次のとおりです。

foo((int*)0); // note: foo(NULL) means foo(0)

nullptrより簡単かつ直感的にできます:

foo(nullptr);

Bjarne の Web ページからの追加リンク。
無関係ですが、C++ 11 の補足事項:

auto p = 0; // makes auto as int
auto p = nullptr; // makes auto as decltype(nullptr)
于 2012-12-11T08:41:35.497 に答える
2

これらのオーバーロードの問題よりも IMO の方が重要です。深くネストされたテンプレート構造では、型を見失うことは難しく、明示的な署名を与えることはかなりの努力です。したがって、使用するすべてのものについて、意図した目的に正確に焦点を合わせるほど、明示的な署名の必要性が減り、何か問題が発生したときにコンパイラがより洞察に満ちたエラー メッセージを生成できるようになります。

于 2012-12-11T08:54:04.270 に答える