`nullptr` vs `NULL`: como o C++ moderno resolveu o problema do ponteiro nulo

Origem do NULL

A macro NULL acompanha o C desde seus primórdios e foi herdada pelo C++ como forma de representar um ponteiro que não aponta para nenhum endereço válido. Sua definição concreta varia entre implementações:

// Definições típicas encontradas em headers padrão
#define NULL ((void*)0)   // estilo C
#define NULL 0            // estilo C++ mais comum

O problema central aparece no segundo caso: quando NULL é apenas o número inteiro 0, o compilador perde a informação de que se tratava de um ponteiro. Isso gera conversões indeseajdas e chamadas ambíguas em código moderno.

A introdução do nullptr

O C++11 introduziu nullptr como um literal de tipo próprio, std::nullptr_t. Ele pode ser convertido para qualquer tipo de ponteiro, mas nunca para um inteiro ou outro tipo aritmético.

int* p = nullptr;
double* q = nullptr;      // converte para double*
// int v = nullptr;       // erro: não converte para inteiro

Comparando comportamentos

Sobrecarga de funções

void setup(int port);
void setup(double* sensor);

setup(NULL);      // ambíguo ou seleciona setup(int)
setup(nullptr);   // setup(double*)

Dedução com auto

auto h1 = NULL;     // h1 é um inteiro
auto h2 = nullptr;  // h2 é std::nullptr_t

Condições booleanas

bool ok1 = NULL;     // compila: false
// bool ok2 = nullptr; // erro de compilação

if (nullptr) { }     // OK: conversão contextual para bool

Impacto em templates

Em templates, a distinção antre NULL e nullptr fica ainda mais evidente. Como NULL pode ser deduzido como inteiro, comparações internas com nullptr podem falhar.

template<typename T>
void inspect(T value) {
    if (value == nullptr) {   // válido apenas para tipos de ponteiro
        // ...
    }
}

inspect(nullptr);   // T é std::nullptr_t: OK
inspect(NULL);      // T é int: comparação inválida

Cenários práticos

Inicialização após desalocação

float* samples = new float[256];
// ... uso ...
delete[] samples;
samples = nullptr;   // previne uso de ponteiro pendente

Construtores que aceitam ponteiros

class Buffer {
public:
    explicit Buffer(int* data);
    explicit Buffer(float* data);
};

// Buffer b1(NULL);       // ambíguo ou padrão errado
Buffer b2(nullptr);       // ambíguo entre ponteiros, mas nunca vira int

Diretrizes de uso

Contexto Recomendação Motivo
Projeto C puro NULL nullptr não faz parte do C
Projeto C++ moderno nullptr Segurança de tipo e clareza
Código C/C++ misto Convênio da equipe Mantenha consistência por arquivo
Após delete/free nullptr Evita ponteiros pendentes

Tags: C++ C nullptr std::nullptr_t C++11

Publicado em 7-2 18:35