C++において「何もないこと」を指し示すポインターの表現には、古くから使われているNULLと、C++11から導入されたnullptrの2種類が存在します。
初心者からベテランまで、多くの開発者が一度は「どちらを使うべきか」という疑問を抱くテーマですが、現代のC++開発においてはnullptrの使用が標準となっています。
本記事では、なぜNULLよりもnullptrが推奨されるのか、その内部構造の違いや関数のオーバーロードにおける挙動の差、そして現代のC++におけるベストプラクティスを網羅的に解説します。
安全でバグの少ないコードを書くための必須知識を身につけていきましょう。
NULLの正体と歴史的背景
まずは、昔から使われているNULLがどのような仕組みで動いているのかを理解しましょう。
NULLは言語仕様上のキーワードではなく、プリプロセッサで定義されたマクロです。

NULLの定義
C言語において、NULLは一般的に(void*)0と定義されています。
しかし、C++ではvoid*から他のポインター型への暗黙の型変換が厳しく制限されているため、多くの実装では単純に整数の0として定義されています。
#include <iostream>
// 一般的なC++環境でのNULLの定義(概念的な例)
#define NULL 0
int main() {
// ポインターに0を代入しているのと同じ
int* ptr = NULL;
if (ptr == 0) {
std::cout << "ptrはヌルポインターです。" << std::endl;
}
return 0;
}
このように、C++におけるNULLは「ポインターとして振る舞うことを期待された整数」に過ぎません。
この「実態が整数である」という点が、型安全性を重視するC++において様々な問題を引き起こす原因となります。
nullptrの導入とメリット
C++11で導入されたnullptrは、NULLが抱えていた問題を解決するために設計されたヌルポインター専用のキーワードです。

nullptrの型(std::nullptr_t)
nullptrの最大の特徴は、それがint型ではなく、std::nullptr_tという専用の型を持っていることです。
nullptrは任意のポインター型に暗黙的に変換可能ですが、整数型(boolを除く)には直接変換できないという性質を持っています。
型安全性の向上
nullptrを使用することで、コンパイラはそれが「ポインターの値」であることを明確に認識できます。
これにより、開発者の意図しない型変換を防止し、コードの可読性と安全性が飛躍的に向上します。
現代のC++コンパイラでは、NULLの使用に対して警告を出す設定も一般的になっており、nullptrこそが真のヌルポインターとして扱われています。
nullptrとNULLの決定的な違い:オーバーロード
NULLとnullptrの違いが最も顕著に現れるのが、関数のオーバーロード(多重定義)です。

NULLによる意図しない呼び出し
次のプログラムを見てください。
整数を引数に取る関数と、ポインターを引数に取る関数がオーバーロードされています。
#include <iostream>
void printValue(int n) {
std::cout << "整数型の関数が呼ばれました: " << n << std::endl;
}
void printValue(int* ptr) {
if (ptr == nullptr) {
std::cout << "ポインター型の関数が呼ばれました(null)" << std::endl;
} else {
std::cout << "ポインター型の関数が呼ばれました" << std::endl;
}
}
int main() {
// NULLを渡した場合
// 実態が0(整数)であるため、意図に反して整数版が選ばれる可能性がある
std::cout << "--- NULLの場合 ---" << std::endl;
printValue(NULL);
// nullptrを渡した場合
// 明確にポインターとして扱われるため、ポインター版が呼ばれる
std::cout << "--- nullptrの場合 ---" << std::endl;
printValue(nullptr);
return 0;
}
--- NULLの場合 ---
整数型の関数が呼ばれました: 0
--- nullptrの場合 ---
ポインター型の関数が呼ばれました(null)
この結果からわかる通り、NULLを渡すと、コンパイラは「整数としての0」を優先してしまい、本来呼び出したかったポインター引数の関数が無視されるという現象が起こります。
一方、nullptrを使用すれば、このような曖昧さは一切発生しません。
テンプレートにおける挙動の違い
ジェネリックプログラミング(テンプレート)を活用する場合、NULLの使用はさらに危険な挙動を示します。

テンプレート関数にNULLを渡すと、型推論によって引数はint型として扱われます。
もし、そのテンプレート内で「引数はポインターであるはずだ」という前提の処理を記述していた場合、コンパイルエラーや実行時の深刻なバグに繋がります。
#include <iostream>
template<typename T>
void checkType(T param) {
// 型の名前を表示(簡略化のためイメージ)
std::cout << "推論された型で処理を実行中..." << std::endl;
}
int main() {
// NULLはintとして推論されることが多い
checkType(NULL);
// nullptrはstd::nullptr_tとして推論される
checkType(nullptr);
return 0;
}
テンプレートを使用する現代的なC++のライブラリやフレームワークでは、型情報が厳密に扱われるため、nullptrを使用しなければ正しく動作しないケースが多々あります。
どちらを使うべきか?使い分けの基準
結論から申し上げますと、C++を書くのであれば、常にnullptrを使用すべきです。
歴史的な理由以外でNULLを選択する積極的なメリットはありません。
比較表:NULL vs nullptr
以下の表に、両者の主な違いをまとめました。
| 特徴 | NULL | nullptr |
|---|---|---|
| 分類 | マクロ(#define) | キーワード(予約語) |
| 実態 | 整数(0) | ポインター専用の型(std::nullptr_t) |
| 導入時期 | C言語からの継承 | C++11以降 |
| 型安全性 | 低い(整数と混同される) | 高い(ポインター専用) |
| 推奨度 | 非推奨(レガシー) | 強く推奨(標準) |
nullptrを使うべき具体的な場面
- ポインター変数の初期化
- 関数に「空のポインター」を渡すとき
- ポインターが有効かどうかをチェックする比較条件(
if (ptr == nullptr)) - 関数の戻り値として「失敗」や「該当なし」をポインターで返すとき
NULLを使わざるを得ない場面
唯一の例外は、C言語とC++の両方でコンパイルする必要があるヘッダーファイルを作成する場合です。
C言語(C23以前)にはnullptrが存在しなかったため、共通コードではNULLが使われることがあります。
しかし、近年のC言語(C23規格)でもnullptrが導入されたため、今後はこの境界線もなくなっていくでしょう。
C++20/23/26における最新動向
2026年現在の視点で見ても、nullptrの重要性は揺るぎません。
むしろ、C++20のコンセプト(Concepts)や、より高度な静的解析ツールの普及により、「型が曖昧なNULL」は排除すべき対象として明確に位置付けられています。

また、C言語においてもC23規格からnullptrが正式に採用されました。
これにより、CとC++の相互運用においてNULLを使わなければならないという制約すら過去のものとなりつつあります。
これから新しく書くコードはもちろん、既存のコードのリファクタリングにおいても、NULLからnullptrへの置き換えは、コードの品質を高める最も手軽で効果的な手段の一つです。
まとめ
C++におけるnullptrとNULLの違いについて解説しました。
古くから使われてきたNULLは整数の0として定義されているマクロであり、関数のオーバーロードやテンプレートにおいて予期せぬ挙動を引き起こすリスクがあります。
一方、C++11で導入されたnullptrは、ポインターであることを明確に示す専用の型を持っており、現代のC++開発における安全性の要となっています。
2026年現在のプログラミングシーンにおいても、「ヌルポインターにはnullptrを使う」というのが鉄則です。
もし現在、NULLを使っている箇所があれば、今日からnullptrに書き換えることをお勧めします。
それだけで、あなたの書くコードはより堅牢で、他のエンジニアにとっても読みやすい高品質なものへと進化するはずです。
