新しいヌル ポインター nullptr は、数値 0 とマクロ NULL のあいまいさによって C++ でクリーンアップされます。
数字の 0
リテラル 0 の問題は、null ポインター (void*)0 または数値 0 になる可能性があることです。これはコンテキスト次第です。私たちはこの奇妙さに慣れていることを認めます。しかし、ほとんどです。
したがって、番号 0 の小さなプログラムは混乱を招くはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // null.cpp #include <iostream> #include <typeinfo> int main(){ std::cout << std::endl; int a= 0; int* b= 0; auto c= 0; std::cout << typeid(c).name() << std::endl; auto res= a+b+c; std::cout << "res: " << res << std::endl; std::cout << typeid(res).name() << std::endl; std::cout << std::endl; } |
問題は、12 行目の変数 c と 15 行目の変数 res のデータ型は何ですか?
変数 c は int 型で、変数 res は int へのポインタ型 int* です。シンプルですね。 15 行目の式 a+b+c はポインター演算です。
マクロ NULL
NULL ポインター NULL の問題は、暗黙的に int に変換されることです。あまり良くありません。
en.cppreference.com によると、マクロ NULL は実装定義のヌル ポインター定数です。可能な実装:
#define NULL 0 //since C++11 #define NULL nullptr
しかし、それは私のプラットフォームには当てはまりません。 Null は long int 型のようです。この点については後述します。マクロ NULL を使用すると、いくつかの疑問が生じます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // nullMacro.cpp #include <iostream> #include <typeinfo> std::string overloadTest(int){ return "int"; } std::string overloadTest(long int){ return "long int"; } int main(){ std::cout << std::endl; int a= NULL; int* b= NULL; auto c= NULL; // std::cout << typeid(c).name() << std::endl; // std::cout << typeid(NULL).name() << std::endl; std::cout << "overloadTest(NULL)= " << overloadTest(NULL) << std::endl; std::cout << std::endl; } |
コンパイラは、19 行目の int への暗黙的な変換について不平を言います。それは問題ありません。しかし、21 行目の警告は紛らわしいものです。コンパイラは、変数 c の型を long int に自動的に推定します。同時に、式 NULL を変換する必要があることを訴えます。私の観察では、26 行目の overloadTest(NULL) の呼び出しに従っています。コンパイラは long int 型のバージョンを使用します (10 行目)。実装で int 型の NULL が使用されている場合、コンパイラは int 型のパラメーターに対して overloadTest を選択します (6 行目)。 C++ 標準によれば、これで問題ありません。
今、ヌルポインタ定数 NULL の現在の型を知りたいです。したがって、プログラムの 22 行目と 23 行目にコメントします。
NULL は、一方では long int 型のコンパイラで、他方では定数ポインタであると思われます。この動作は、プログラム nullMacro.cpp のコンパイルを示しています。
私は自分の教訓を学びました。マクロ NULL を使用しないでください。
しかし、新しいヌル ポインター定数 nullptr を使用することで、問題を解決できます。
ヌル ポインター定数 nullptr
新しいヌル ポインター nullptr は、数値 0 とマクロ NULL のあいまいさを使用して C++ でクリーンアップされます。 nullptr は std::nullptr_t 型のままです。
nullptr には任意のポインターを割り当てることができます。ポインターはヌル ポインターになり、データを指しません。 nullptr を逆参照することはできません。このタイプのポインターは、一方ではすべてのポインターと比較でき、他方ではすべてのポインターに変換できます。これは、クラス メンバーへのポインターにも当てはまります。ただし、nullptr を整数型に比較して変換することはできません。この規則には 1 つの例外があります。 bool 値を nullptr と暗黙的に比較および変換できます。したがって、論理式で nullptr を使用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // nullptr.cpp #include <iostream> #include <string> std::string overloadTest(char*){ return "char*"; } std::string overloadTest(long int){ return "long int"; } int main(){ std::cout << std::endl; long int* pi = nullptr; // long int i= nullptr; // ERROR auto nullp= nullptr; // type std::nullptr_t bool b = nullptr; std::cout << std::boolalpha << "b: " << b << std::endl; auto val= 5; if ( nullptr < &val ){ std::cout << "nullptr < &val" << std::endl; } // calls char* std::cout << "overloadTest(nullptr)= " << overloadTest(nullptr)<< std::endl; std::cout << std::endl; } |
nullptr を使用して、long int 型のポインターを初期化できます (18 行目)。ただし、long int 型の変数の初期化には使用できません (18 行目)。 20 行目の自動型推定は非常に興味深いものです。 nullp は std::nullptr_t 型の値になります。 null ポインター定数は、false で初期化されたブール値のように動作します。行 22 ~ 25 で確認できます。nullptr が long int とポインターのどちらかを決定する必要がある場合は、ポインターを決定します (行 28)。
これがプログラムの出力です。
簡単なルールは次のとおりです。0 または NULL の代わりに nullptr を使用してください。 それでも、納得できませんか?これが私の最後の最強ポイントです。
汎用コード
リテラル 0 と NULL は、汎用コードでその真の性質を示しています。テンプレート引数推定のおかげで、両方のリテラルは関数テンプレートの整数型です。両方のリテラルが null ポインター定数であったというヒントはありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // generic.cpp #include <cstddef> #include <iostream> template<class P > void functionTemplate(P p){ int* a= p; } int main(){ int* a= 0; int* b= NULL; int* c= nullptr; functionTemplate(0); functionTemplate(NULL); functionTemplate(nullptr); } |
12 行目と 13 行目で、0 と NULL を使用して int ポインターを初期化できます。ただし、値 0 と NULL を関数テンプレートの引数として使用すると、コンパイラーは大声で文句を言います。コンパイラは、関数テンプレートの 0 を int 型と推定します。 NULL を long int 型に推定します。しかし、これらの観察は nullptr には当てはまりません。 nullptr は std::nullptr_t 型の 12 行目にあり、nullptr は std::nullptr_t 型の 8 行目にあります。
次は?
前回の投稿で、コードをより安全にするための C++ の多くの機能を紹介しました。どれ?概要ページで高い安全要件を確認してください。これらすべての機能の重要なアイデアは、コンパイラのスマートさを利用することです。したがって、C++ の重要な原則の 1 つに従います。コンパイル時のエラーは実行時のエラーよりも優れています。
次の投稿で、私は焦点を切り替えます。私の焦点は、安全上重要な機能にとって重要な C++ 機能から、パフォーマンス上の理由で重要な機能に変わります。次の投稿では、インラインについて詳しく説明します。キーワード inline のおかげで、コンパイラは関数呼び出しをその関数呼び出しに置き換えることができます。したがって、関数の高価な呼び出しは不要になります。