nullptrとは、C++11で導入された、ヌルポインタを表すための専用リテラルです。
簡単にいえば、「このポインタは現在、有効なオブジェクトを指していない」という状態を表すために使います。
たとえば、次のように記述します。
int* ptr = nullptr;
このコードでは、ptrはint型の値を指すためのポインタですが、現時点では何も指していません。
C++11以降では、ヌルポインタを表す際にNULLや0ではなく、基本的にnullptrを使うことが推奨されています。
nullptrの意味
nullptrはヌルポインタリテラル
厳密には、nullptrそのものがポインタというわけではありません。
nullptrは「ヌルポインタリテラル」と呼ばれる特別な値です。
型は次のとおりです。
std::nullptr_t
nullptrは、さまざまなポインタ型のヌルポインタ値へ変換できます。
たとえば、次のコードはいずれも有効です。
int* p1 = nullptr;
double* p2 = nullptr;
char* p3 = nullptr;
クラスへのポインタにも使えます。
class User
{
};
User* user = nullptr;
つまり、nullptrは特定のポインタ型専用ではなく、「有効な参照先がない」という状態を共通して表現するための仕組みです。
nullptrが導入された理由
C++11以前は0やNULLが使われていた
C++11より前では、ヌルポインタを表すために0やNULLがよく使われていました。
int* p1 = 0;
int* p2 = NULL;
これらの書き方も、多くの場合はヌルポインタとして機能します。
しかし、0やNULLは整数として扱われる可能性があるため、型の判定や関数オーバーロードで問題が起こることがあります。
nullptrならポインタである意図が明確になる
nullptrはヌルポインタを表すために設計された専用リテラルです。
そのため、次のコードを見れば、整数の0ではなく「何も指していないポインタを表している」と明確に分かります。
int* ptr = nullptr;
可読性と型安全性の両面で、NULLや0より優れています。
nullptrとNULLの違い
NULLは実装依存のマクロ
NULLはC言語や従来のC++で広く使われてきました。
環境によっては、概念的に次のような形で定義されていることがあります。
#define NULL 0
ただし、実際の定義は処理系や言語モードによって異なります。
そのため、「NULLは必ず整数の0である」と断定するのは正確ではありません。
重要なのは、C++ではNULLが整数型として扱われるケースがあり、オーバーロード解決などで意図しない挙動につながる可能性があることです。
nullptrには専用の型がある
一方、nullptrには専用の型があります。
std::nullptr_t
これにより、整数の0とヌルポインタを明確に区別できます。
nullptrと0の違い
ポインタ初期化ではどちらも使える
次のコードはいずれも有効です。
int* p1 = 0;
int* p2 = nullptr;
どちらも結果的にはヌルポインタになります。
ただし、型安全性や可読性を考えると、C++11以降ではnullptrを使う方が適切です。
nullptrは整数ではない
次のコードはエラーになります。
int value = nullptr;
nullptrは整数値ではないため、通常のint型変数へそのまま代入することはできません。
一方、ポインタであれば問題ありません。
int* ptr = nullptr;
この違いによって、コンパイラは「整数の0」と「ヌルポインタ」を区別できます。
関数オーバーロードにおけるnullptrのメリット
0では整数版の関数が選ばれる
次のようなオーバーロードを考えてみます。
#include <iostream>
void func(int)
{
std::cout << "int\n";
}
void func(int*)
{
std::cout << "pointer\n";
}
int main()
{
func(0);
}
この場合、0はint型の整数リテラルなので、次の関数が選択されます。
void func(int);
もしプログラマーが「ヌルポインタを渡したつもり」だった場合、意図とは異なる結果になります。
nullptrならポインタ版が選ばれる
nullptrを使うと、意図が明確になります。
#include <iostream>
void func(int)
{
std::cout << "int\n";
}
void func(int*)
{
std::cout << "pointer\n";
}
int main()
{
func(nullptr);
}
この場合は、次のポインタを受け取る関数が選ばれます。
void func(int*);
このように、関数オーバーロードにおける曖昧さを減らせる点は、nullptrの大きなメリットです。
nullptrの基本的な使い方
ポインタの初期化に使う
ポインタの参照先がまだ決まっていない場合は、nullptrで初期化できます。
int* ptr = nullptr;
次のように初期化しないまま宣言するより安全です。
int* ptr;
ローカル変数として宣言された未初期化ポインタは、不定な値を持つ可能性があります。
誤って使用すると、未定義動作につながる可能性があります。
そのため、参照先が決まっていない場合は、最初からnullptrで初期化しておく方が安全です。
リスト初期化でも書ける
現代C++では、次のような書き方もできます。
int* ptr{nullptr};
意味は同じです。
nullptrかどうかを判定する方法
== nullptrで判定できる
ポインタがヌルポインタかどうかは、次のように確認できます。
int* ptr = nullptr;
if (ptr == nullptr)
{
std::cout << "何も指していません\n";
}
逆に、ヌルポインタではないことを確認する場合は次のように書けます。
if (ptr != nullptr)
{
std::cout << *ptr << '\n';
}
if(ptr)でも判定できる
ポインタは条件式の中で判定できます。
if (ptr)
{
std::cout << *ptr << '\n';
}
ptrがヌルポインタならfalseとして扱われ、ヌルポインタでなければtrueとして扱われます。
逆に、次のようにも書けます。
if (!ptr)
{
std::cout << "ptrはnullptrです\n";
}
実際のC++コードでは、if (ptr)やif (!ptr)という書き方もよく使われます。
nullptrをデリファレンスしてはいけない
nullptrの参照先にはアクセスできない
次のコードは危険です。
int* ptr = nullptr;
std::cout << *ptr << '\n';
*ptrは、ポインタが指しているオブジェクトへアクセスする操作です。
しかし、ptrは有効なオブジェクトを指していません。
そのため、ヌルポインタをデリファレンスすると未定義動作になります。
必ずクラッシュするとは限らない
ヌルポインタをデリファレンスすると、実際の環境ではプログラムがクラッシュすることがあります。
ただし、C++規格として「必ずクラッシュする」と保証されているわけではありません。
未定義動作であるため、どのような結果になるかは保証されません。
必要に応じて、デリファレンスする前にポインタを確認します。
if (ptr != nullptr)
{
std::cout << *ptr << '\n';
}
nullptrを代入してもオブジェクトは削除されない
ポインタの参照先が変わるだけ
次のコードを考えてみます。
int value = 100;
int* ptr = &value;
ptr = nullptr;
この場合、ptrはvalueを指さなくなります。
しかし、valueそのものが削除されるわけではありません。
nullptrをポインタへ代入することと、オブジェクトの寿命を終了させることは別の処理です。
delete後にnullptrを代入する使い方
deleteしただけではポインタはnullptrにならない
次のコードを考えてみます。
int* ptr = new int(100);
delete ptr;
deleteによって動的に確保したオブジェクトは破棄されます。
しかし、ptrという変数自体が自動的にnullptrになるわけではありません。
この時点のptrは、すでに寿命が終了したオブジェクトのアドレス値を保持しているダングリングポインタになります。
そこで、次のように書くことがあります。
delete ptr;
ptr = nullptr;
これにより、そのptrという変数については「有効な参照先を持っていない」という状態を明確にできます。
他のポインタまで安全になるわけではない
ただし、nullptrを代入すればすべて安全になるわけではありません。
int* p1 = new int(100);
int* p2 = p1;
delete p1;
p1 = nullptr;
この場合、p1はnullptrになります。
しかし、p2は解放済みオブジェクトのアドレスを保持したままです。
つまり、p2はダングリングポインタです。
p1 = nullptr;によって安全になるのは、あくまでp1という変数についてだけです。
現代C++ではスマートポインタを使う
unique_ptrにもnullptrを使える
現代C++では、単純な所有権管理のためにnewとdeleteを直接扱うより、std::unique_ptrなどのスマートポインタを使う方が一般的です。
#include <memory>
std::unique_ptr<int> ptr = nullptr;
この場合、ptrはオブジェクトを所有していません。
オブジェクトを生成する場合は、次のようにstd::make_uniqueを使えます。
auto ptr = std::make_unique<int>(100);
スマートポインタを空に戻すこともできる
std::unique_ptrを空の状態に戻す場合は、reset()を使えます。
ptr.reset();
また、次のようにnullptrを代入することもできます。
ptr = nullptr;
所有中のオブジェクトがある場合は適切に破棄され、その後スマートポインタは空の状態になります。
std::nullptr_tとは
nullptr専用の型
nullptrの型はstd::nullptr_tです。
std::nullptr_tは<cstddef>で定義されています。
#include <cstddef>
std::nullptr_t nullValue = nullptr;
通常のアプリケーションコードでstd::nullptr_t型の変数を直接作る機会は多くありません。
しかし、テンプレートやオーバーロードなど、型を厳密に扱う処理では理解しておくと役立ちます。
decltypeでも型を確認できる
nullptrの型は、decltypeを使って確認できます。
decltype(nullptr)
これはstd::nullptr_tと同じ型です。
void*にもnullptrを使える
voidポインタのヌル値にも使える
nullptrはvoid*にも変換できます。
void* ptr = nullptr;
そのため、void*を扱うC言語由来のAPIなどでも利用できます。
ただし、外部APIを使用する場合は、そのAPIが求めている引数の仕様を確認する必要があります。
メンバーポインタにもnullptrを使える
通常のポインタ以外にも使用可能
nullptrは通常のオブジェクトポインタだけでなく、メンバーポインタ型にも使用できます。
class Sample
{
public:
int value;
};
int Sample::* ptr = nullptr;
日常的なC++プログラミングでは登場頻度の低い機能ですが、nullptrが単なる整数の0ではないことを理解するうえでは重要です。
関数の引数としてnullptrを使う
「対象がない」ことを表現できる
ポインタを引数に取る関数では、nullptrを使って「対象となるオブジェクトがない」ことを表現できます。
#include <iostream>
void printValue(const int* ptr)
{
if (ptr == nullptr)
{
std::cout << "値がありません\n";
return;
}
std::cout << *ptr << '\n';
}
呼び出し側では次のように使えます。
int value = 100;
printValue(&value);
printValue(nullptr);
ただし、「値がある場合とない場合」を表現する目的であれば、設計によってはC++17以降のstd::optionalなどが適切なケースもあります。
関数の戻り値としてnullptrを使う
「見つからなかった」を表現できる
ポインタを返す関数では、対象が存在しなかった場合にnullptrを返す設計があります。
int* findValue(int* values, int size, int target)
{
for (int i = 0; i < size; ++i)
{
if (values[i] == target)
{
return &values[i];
}
}
return nullptr;
}
呼び出し側では、戻り値を確認してから使用します。
int values[] = {10, 20, 30};
int* result = findValue(values, 3, 20);
if (result != nullptr)
{
std::cout << *result << '\n';
}
見つからなかった場合はnullptrが返されるため、結果の有無を判定できます。
nullptrチェックだけではポインタの安全性を保証できない
nullptrではなくても無効なポインタは存在する
次のコードを考えてみます。
int* ptr = new int(100);
delete ptr;
if (ptr != nullptr)
{
// ptrはnullptrではない可能性がある
}
delete後のptrは、自動的にはnullptrになりません。
そのため、ptr != nullptrであっても、参照先が有効とは限りません。
ダングリングポインタはnullptrチェックでは検出できない
nullptrチェックで分かるのは、基本的に「ヌルポインタ値かどうか」です。
ポインタが解放済みオブジェクトを指しているかどうかや、参照先の寿命が終了しているかどうかまでは判定できません。
したがって、次のように考えるのは危険です。
if (ptr != nullptr)
{
// 必ず安全とは限らない
}
ポインタの安全性を確保するには、オブジェクトの寿命や所有権も適切に管理する必要があります。
nullptrとNULLと0の違い
主な違いを比較
nullptr、NULL、0の違いを整理すると、次のようになります。
| 書き方 | 主な意味 | 型安全性 | C++11以降での推奨度 |
|---|---|---|---|
nullptr | ヌルポインタを表す専用リテラル | 高い | 推奨 |
NULL | 従来のヌルポインタ表現 | nullptrより低い | 新規コードでは原則避ける |
0 | 整数0、またはヌルポインタ定数 | nullptrより低い | 新規コードでは原則避ける |
既存の古いC++コードではNULLや0を見ることがありますが、新しくコードを書く場合はnullptrを基本にするとよいでしょう。
nullptrを使う主な場面
参照先がまだ決まっていない場合
User* user = nullptr;
ポインタを安全な初期状態にしておきたい場合に使えます。
オブジェクトが存在しないことを表す場合
process(nullptr);
関数へ「対象なし」という意味を渡したい場合に利用できます。
検索失敗などを戻り値で表す場合
return nullptr;
ポインタを返す関数で、「該当するオブジェクトがなかった」という意味を表現できます。
ポインタを空の状態に戻したい場合
ptr = nullptr;
現在の参照先を持たない状態に戻したい場合に使えます。
nullptrを使った実用例
値を検索してポインタを返す
次のプログラムでは、配列から指定された値を検索しています。
#include <iostream>
int* findNumber(int* numbers, int size, int target)
{
for (int i = 0; i < size; ++i)
{
if (numbers[i] == target)
{
return &numbers[i];
}
}
return nullptr;
}
int main()
{
int numbers[] = {10, 20, 30, 40, 50};
int* result = findNumber(numbers, 5, 30);
if (result != nullptr)
{
std::cout << "見つかりました: " << *result << '\n';
}
else
{
std::cout << "見つかりませんでした\n";
}
return 0;
}
findNumber()は、目的の値を見つけた場合、その要素へのポインタを返します。
見つからなかった場合は、次のようにnullptrを返します。
return nullptr;
呼び出し側では、戻り値がnullptrでないことを確認してからデリファレンスしています。
if (result != nullptr)
{
std::cout << *result << '\n';
}
このように「対象が存在する場合はポインタを返し、存在しない場合はnullptrを返す」という設計は、C++でよく使われるパターンです。
C++11以降ではnullptrを使うのが基本
C++11以降のコードでは、ヌルポインタを表す際は次の書き方を基本にするとよいでしょう。
int* ptr = nullptr;
古いコードでは、次のような書き方を見ることがあります。
int* ptr = NULL;
または、
int* ptr = 0;
これらも既存コードでは珍しくありません。
しかし、新しいC++コードでは、特別な理由がない限りnullptrを選ぶのが適切です。
nullptrについて覚えておきたいポイント
nullptrは、単に「NULLの新しい書き方」ではありません。
C++11で導入された、ヌルポインタを表すための型安全な専用リテラルです。
特に重要なのは、次のポイントです。
nullptrはC++11で導入されたヌルポインタリテラルです。nullptrの型はstd::nullptr_tです。int* ptr = nullptr;のように、参照先を持たないポインタを表現できます。NULLや0よりも型安全で、関数オーバーロードの問題を避けやすくなります。nullptrをデリファレンスすると未定義動作になります。ptr == nullptrやif (!ptr)などでヌルポインタかどうかを確認できます。nullptrを代入しても、元のオブジェクトが自動的に削除されるわけではありません。delete後にポインタが自動的にnullptrになるわけでもありません。nullptrチェックだけではダングリングポインタなどの問題を検出できません。- C++11以降の新規コードでは、原則として
NULLや0よりnullptrを使うのがおすすめです。
C++のポインタを扱ううえでは、「参照先がない状態をnullptrで明示する」「ヌルポインタをデリファレンスしない」「ポインタの寿命や所有権にも注意する」という3点を意識すると、安全で読みやすいコードを書きやすくなります。
以上、C++のnullptrの意味や使い方についてでした。
最後までお読みいただき、ありがとうございました。
