【C++】nullptrとNULLの違いとは?|使い分けをわかりやすく解説

サムネイル画像 C++

今回は、C++での「nullptr と NULL の違い」について解説していきます。

ポインタをNULLで初期化していたら「nullptrを使うべき」と言われたことはありませんか?

この記事を読み終えると、あなたはnullptrとNULLの違いを理解し、正しく使い分けられるようになると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

nullptrとは?

nullptrとは、

・「このポインタは何も指していませんよ」
明示的にヌルポインタを表すためのキーワード

です。

C++11から導入された新しいヌルポインタ定数で、従来のNULLよりも安全で明確な書き方が可能になります。

NULLとは?

NULLは、C言語の時代から使われてきたマクロです。

通常、以下のように定義されています。

#define NULL 0

つまり、NULLは実質的に「0」として扱われます

これが、後で説明する問題の原因になります。

基本的な使い方

まずは、nullptrとNULLの基本的な使い方を見てみましょう。

▼main.cpp

#include <iostream>

int main() {
    int* ptr1 = NULL;      // 古い書き方
    int* ptr2 = nullptr;   // C++11以降推奨
    
    if (ptr1 == nullptr) {
        std::cout << "ptr1 is null";
    }
    
    if (ptr2 == nullptr) {
        std::cout << "ptr2 is null";
    }
    
    return 0;
}

実行結果

ptr1 is nullptr2 is null

どちらも同じように動作しますが、実は重要な違いがあります。

NULLの問題点

1. 関数のオーバーロードで曖昧になる

NULLは「0」として定義されているため、整数とポインタのどちらを受け取る関数か判別できない場合があります。

▼main.cpp

#include <iostream>

void func(int num) {
    std::cout << "int version: " << num;
}

void func(int* ptr) {
    std::cout << "pointer version";
}

int main() {
    func(0);       // int版が呼ばれる
    func(NULL);    // int版が呼ばれる(予想外!)
    func(nullptr); // pointer版が呼ばれる
    
    return 0;
}

実行結果

int version: 0int version: 0pointer version

func(NULL)では、ポインタ版ではなく整数版が呼ばれてしまいます

これは、NULLが「0」として扱われるためです。

一方、nullptrを使えば、必ずポインタ版が呼ばれます

2. 型安全性がない

NULLは整数として扱われるため、誤って整数と比較してもエラーになりません

▼main.cpp

#include <iostream>

int main() {
    int num = 0;
    
    if (num == NULL) {  // 警告は出るが、コンパイルは通る
        std::cout << "This works but shouldn't";
    }
    
    // if (num == nullptr) { // エラー!整数とポインタは比較できない
    
    return 0;
}

NULLを使うと、整数とポインタを誤って比較してもコンパイルが通ってしまいます

しかし、nullptrを使えばコンパイルエラーになるため、バグを防げます。

nullptrの利点

nullptrには、以下のような利点があります。

  • 型安全:ポインタ型専用なので、整数との混同がない
  • オーバーロード対応:関数のオーバーロードで正しく判別される
  • 明確性:コードを読む人に「これはポインタだ」と明示できる
  • モダンC++:C++11以降の標準的な書き方

テンプレートでの違い

テンプレートを使う場合、nullptrとNULLの違いがさらに重要になります。

▼main.cpp

#include <iostream>

template<typename T>
void process(T value) {
    if (value == nullptr) {
        std::cout << "Received null pointer";
    }
}

int main() {
    int* ptr = nullptr;
    
    process(ptr);     // OK
    process(nullptr); // OK
    // process(NULL); // NULLは整数として推論される可能性がある
    
    return 0;
}

テンプレートでは、NULLは整数型として推論される場合があるため、nullptrを使う方が安全です。

実践的な使い方

実際のコードでは、以下のようにnullptrを使います。

▼main.cpp

#include <iostream>

class Node {
public:
    int value;
    Node* next;
    
    Node(int val) : value(val), next(nullptr) {}
};

int main() {
    Node* head = nullptr;
    
    // 新しいノードを作成
    head = new Node(10);
    head->next = new Node(20);
    
    // リストを巡回
    Node* current = head;
    while (current != nullptr) {
        std::cout << current->value << " ";
        current = current->next;
    }
    
    // メモリ解放
    delete head->next;
    delete head;
    
    return 0;
}

実行結果

10 20

このように、ポインタの初期化や条件判定にはnullptrを使うのが推奨されます。

どちらを使うべき?

C++11以降では、必ずnullptrを使うべきです。

理由は以下の通りです。

  • 型安全:整数とポインタの混同を防げる
  • 明確性:ポインタであることが一目でわかる
  • 標準:モダンC++の標準的な書き方
  • バグ防止:オーバーロードやテンプレートで正しく動作する

NULLは古いコードとの互換性のために残されていますが、新しいコードではnullptrを使いましょう

まとめ

nullptrNULLの違いをまとめます。

  • NULL:C言語から使われている古いマクロ。実質的に「0」
  • nullptr:C++11から導入されたポインタ専用のキーワード
  • NULLは整数として扱われるため、オーバーロードで問題が起きる
  • nullptrは型安全で、ポインタ専用として明確
  • C++11以降ではnullptrを使うべき

これでnullptrとNULLの違いはバッチリですね!

モダンなC++を書く際は、ぜひnullptrを活用してみてください。

ここまで読んでくださり、ありがとうございました。

この記事が皆様の学習に役立てば幸いです。