Conceitualmente, esse trecho de código faz a mesma coisa para os três ponteiros (inicialização segura do ponteiro):
int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;
E então, quais são as vantagens de atribuir ponteiros nullptr
sobre a atribuição de valores NULL
ou 0
?
int
evoid *
não escolhe aint
versão sobre avoid *
versão ao usarnullptr
.f(nullptr)
é diferente def(NULL)
. Mas no que diz respeito ao código acima (atribuindo a uma variável local), todos os três ponteiros são exatamente iguais. A única vantagem é a legibilidade do código.nullptr
butthat é a diferença entre 0 eNULL
Respostas:
Nesse código, não parece haver uma vantagem. Mas considere as seguintes funções sobrecarregadas:
Qual função será chamada? Claro, a intenção aqui é chamar
f(char const *)
, mas na realidadef(int)
será chamado! Esse é um grande problema 1 , não é?Portanto, a solução para esses problemas é usar
nullptr
:Claro, essa não é a única vantagem de
nullptr
. Aqui está outro:Como no modelo, o tipo de
nullptr
é deduzido comonullptr_t
, então você pode escrever isso:1. Em C ++,
NULL
é definido como#define NULL 0
, basicamenteint
, é por isso quef(int)
é chamado.fonte
nullptr
? (Não, eu não estou exigindo)NULL
é exigido pelo padrão para ter um tipo integral, e é por isso que geralmente é definido como0
ou0L
. Também não tenho certeza se gosto dessanullptr_t
sobrecarga, pois ela captura apenas chamadas comnullptr
, não com um ponteiro nulo de um tipo diferente, como(void*)0
. Mas posso acreditar que ele tem alguns usos, mesmo que tudo o que faça seja salvar você definindo um tipo de espaço reservado próprio para significar "nenhum".nullptr
tenha um valor numérico bem definido, enquanto as constantes de ponteiro nulo não. Uma constante de ponteiro nulo é convertida no ponteiro nulo desse tipo (seja o que for). É necessário que dois ponteiros nulos do mesmo tipo sejam comparados de forma idêntica, e a conversão booleana transforma um ponteiro nulo emfalse
. Nada mais é necessário. Portanto, é possível para um compilador (bobo, mas possível) usar, por exemplo,0xabcdef1234
ou algum outro número para o ponteiro nulo. Por outro lado,nullptr
é necessário converter para zero numérico.f(nullptr)
não chamará a função pretendida? Havia mais de uma motivação. Muitas outras coisas úteis podem ser descobertas pelos próprios programadores nos próximos anos. Portanto, você não pode dizer que existe apenas um uso verdadeiro denullptr
.O C ++ 11 introduz
nullptr
, é conhecido comoNull
constante de ponteiro e melhora a segurança do tipo e resolve situações ambíguas, diferentemente da constante de ponteiro nulo dependente da implementação existenteNULL
. Ser capaz de entender as vantagens denullptr
. primeiro precisamos entender o que éNULL
e quais são os problemas associados a ele.O que é
NULL
exatamente?O pré C ++ 11
NULL
foi usado para representar um ponteiro que não tem valor ou ponteiro que não aponta para nada válido. Ao contrário da noção popular,NULL
não é uma palavra-chave em C ++ . É um identificador definido nos cabeçalhos da biblioteca padrão. Em resumo, você não pode usarNULL
sem incluir alguns cabeçalhos de biblioteca padrão. Considere o programa de amostra :Resultado:
O padrão C ++ define NULL como uma macro definida por implementação definida em certos arquivos de cabeçalho da biblioteca padrão. A origem de NULL é de C e C ++ a herdou de C. O padrão C definiu NULL como
0
ou(void *)0
. Mas em C ++ há uma diferença sutil.C ++ não pôde aceitar esta especificação como ela é. Ao contrário de C, C ++ é uma linguagem fortemente tipada (C não requer conversão explícita de
void*
para qualquer tipo, enquanto C ++ exige uma conversão explícita). Isso torna a definição de NULL especificada pelo padrão C inútil em muitas expressões C ++. Por exemplo:Se NULL foi definido como
(void *)0
, nenhuma das expressões acima funcionaria.void *
parastd::string
.void *
é necessário converter de para ponteiro para a função de membro.Portanto, diferentemente de C, o C ++ Standard exigia definir NULL como literal numérico
0
ou0L
.Então, qual é a necessidade de outro constante nulo de ponteiro quando
NULL
já o temos ?Embora o comitê de padrões do C ++ tenha apresentado uma definição NULL que funcione para o C ++, essa definição teve seu próprio quinhão de problemas. NULL funcionou bem o suficiente para quase todos os cenários, mas não todos. Deu resultados surpreendentes e errôneos para certos cenários raros. Por exemplo :
Resultado:
Claramente, a intenção parece ser chamar a versão que leva
char*
como argumento, mas como a saída mostra a função que leva umaint
versão é chamada. Isso ocorre porque NULL é um literal numérico.Além disso, como é definido pela implementação se NULL é 0 ou 0L, pode haver muita confusão na resolução da sobrecarga de função.
Programa de exemplo:
Analisando o snippet acima:
doSomething(char *)
conforme o esperado.doSomething(int)
mas talvez achar*
versão tenha sido desejada porque0
IS também é um ponteiro nulo.NULL
for definido como0
, chamadoSomething(int)
quando talvezdoSomething(char *)
pretendido, talvez resultando em erro lógico no tempo de execução. SeNULL
definido como0L
, a chamada é ambígua e resulta em erro de compilação.Portanto, dependendo da implementação, o mesmo código pode gerar vários resultados, o que é claramente indesejado. Naturalmente, o comitê de padrões do C ++ queria corrigir isso e essa é a principal motivação do nullptr.
Então, o que é
nullptr
e como evita os problemasNULL
?O C ++ 11 introduz uma nova palavra
nullptr
- chave para servir como constante de ponteiro nulo. Ao contrário de NULL, seu comportamento não é definido pela implementação. Não é uma macro, mas tem seu próprio tipo. nullptr tem o tipostd::nullptr_t
. C ++ 11 define apropriadamente propriedades para o nullptr para evitar as desvantagens de NULL. Para resumir suas propriedades:Propriedade 1: possui seu próprio tipo
std::nullptr_t
ePropriedade 2: é implicitamente conversível e comparável a qualquer tipo de ponteiro ou ponteiro para membro, mas
Propriedade 3: não é implicitamente conversível ou comparável a tipos integrais, exceto
bool
.Considere o seguinte exemplo:
No programa acima,
char *
Versão de chamadas , Propriedade 2 e 3Assim, a introdução do nullptr evita todos os problemas do bom e velho NULL.
Como e onde você deve usar
nullptr
?A regra de ouro para o C ++ 11 é simplesmente começar a usar
nullptr
sempre que você teria usado NULL no passado.Referências padrão:
Padrão C ++ 11: Macro C.3.2.4 NULL
Padrão C ++ 11: 18.2 Tipos
Padrão C ++ 11: 4.10 Conversões de ponteiro
Padrão C99: 6.3.2.3 Ponteiros
fonte
nullptr
, embora eu não soubesse que diferença realmente faz para o meu código. Obrigado pela ótima resposta e especialmente pelo esforço. Me trouxe muita luz sobre o assunto.0xccccc....
, mas uma variável sem valor é uma contradição inerente.bool flag = nullptr;
). Não, tudo bem, eu recebo o seguinte erro em tempo de compilação com o g ++ 6:error: converting to ‘bool’ from ‘std::nullptr_t’ requires direct-initialization [-fpermissive]
A verdadeira motivação aqui é o encaminhamento perfeito .
Considerar:
Simplificando, 0 é um valor especial , mas os valores não podem se propagar pelos tipos somente do sistema. As funções de encaminhamento são essenciais e 0 não pode lidar com elas. Assim, era absolutamente necessário introduzir
nullptr
, onde o tipo é o que é especial, e o tipo pode realmente se propagar. De fato, a equipe do MSVC teve que se apresentar comnullptr
antecedência depois de implementar as referências de valor e depois descobrir essa armadilha para si.Existem alguns outros casos de canto em
nullptr
que a vida é mais fácil - mas não é um caso essencial, pois o elenco pode resolver esses problemas. ConsiderarChama duas sobrecargas separadas. Além disso, considere
Isso é ambíguo. Mas, com o nullptr, você pode fornecer
fonte
forward((int*)0)
trabalho. Estou esquecendo de algo?Noções básicas de nullptr
std::nullptr_t
é o tipo do literal de ponteiro nulo, nullptr. É um prvalue / rvalue do tipostd::nullptr_t
. Existem conversões implícitas de nullptr para valor de ponteiro nulo de qualquer tipo de ponteiro.O literal 0 é um int, não um ponteiro. Se o C ++ se encontrar olhando para 0 em um contexto onde apenas um ponteiro pode ser usado, interpretará de má vontade 0 como um ponteiro nulo, mas essa é uma posição de fallback. A política principal do C ++ é que 0 é um int, não um ponteiro.
Vantagem 1 - Remova a ambiguidade ao sobrecarregar nos tipos ponteiro e integral
No C ++ 98, a principal implicação disso era que a sobrecarga nos tipos ponteiro e integral poderia levar a surpresas. Passar 0 ou NULL a essas sobrecargas nunca chamou sobrecarga de ponteiro:
O interessante dessa chamada é a contradição entre o significado aparente do código-fonte ("Estou chamando diversão com NULL - o ponteiro nulo") e seu significado real ("Estou chamando diversão com algum tipo de número inteiro - não o nulo ponteiro ").
A vantagem do nullptr é que ele não possui um tipo integral. A diversão da função sobrecarregada com nullptr chama a sobrecarga void * (ou seja, a sobrecarga do ponteiro), porque nullptr não pode ser visto como algo integral:
Usar nullptr em vez de 0 ou NULL evita surpresas na resolução de sobrecarga.
Outra vantagem de
nullptr
maisNULL(0)
quando se usa auto para o tipo de retornoPor exemplo, suponha que você encontre isso em uma base de código:
Se você não souber (ou não conseguir descobrir facilmente) o que findRecord retorna, pode não estar claro se o resultado é um tipo de ponteiro ou um tipo integral. Afinal, 0 (cujo resultado é testado) poderia ser de qualquer maneira. Se você vir o seguinte, por outro lado,
não há ambiguidade: o resultado deve ser do tipo ponteiro.
Vantagem 3
O programa acima é compilado e executado com êxito, mas lockAndCallF1, lockAndCallF2 e lockAndCallF3 têm código redundante. É uma pena escrever um código como este se pudermos escrever um modelo para tudo isso
lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Portanto, pode ser generalizado com o modelo. Eu escrevi a função de modelo emlockAndCall
vez de várias definiçõeslockAndCallF1, lockAndCallF2 & lockAndCallF3
para código redundante.O código é re-fatorado como abaixo:
Análise detalhada por que a compilação falhou,
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
não paralockAndCall(f3, f3m, nullptr)
Por que a compilação de
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
falhou?O problema é que, quando 0 é passado para lockAndCall, a dedução do tipo de modelo entra em ação para descobrir seu tipo. O tipo 0 é int, portanto esse é o tipo do parâmetro ptr dentro da instanciação dessa chamada para lockAndCall. Infelizmente, isso significa que, na chamada para funcionar dentro de lockAndCall, um int está sendo passado e isso não é compatível com o
std::shared_ptr<int>
parâmetrof1
esperado. O 0 passado na chamada paralockAndCall
era destinado a representar um ponteiro nulo, mas o que realmente foi passado foi int. Tentar passar essa int para f1 como astd::shared_ptr<int>
é um erro de tipo. A chamada paralockAndCall
com 0 falha porque, dentro do modelo, um int está sendo passado para uma função que requer astd::shared_ptr<int>
.A análise da chamada envolvendo
NULL
é essencialmente a mesma. QuandoNULL
é passado paralockAndCall
, um tipo integral é deduzido para o parâmetro ptr, e ocorre um erro de tipo quandoptr
- um tipo int ou int-like - é passado paraf2
, o qual espera obter astd::unique_ptr<int>
.Por outro lado, a ligação envolvida
nullptr
não apresenta problemas. Quandonullptr
é passado paralockAndCall
, o tipo deptr
é deduzido como sendostd::nullptr_t
. Quandoptr
é passado paraf3
, há uma conversão implícita destd::nullptr_t
paraint*
, porquestd::nullptr_t
converte implicitamente em todos os tipos de ponteiro.É recomendável, sempre que você desejar se referir a um ponteiro nulo, use nullptr, não 0 ou
NULL
.fonte
Não há vantagem direta de ter
nullptr
da maneira que você mostrou os exemplos.Mas considere uma situação em que você tem 2 funções com o mesmo nome; 1 leva
int
e outro umint*
Se você deseja chamar
foo(int*)
passando um NULL, então o caminho é:nullptr
torna mais fácil e intuitivo :Link adicional da página de Bjarne.
Irrelevante, mas na nota lateral do C ++ 11:
fonte
decltype(nullptr)
éstd::nullptr_t
.typedef decltype(nullptr) nullptr_t;
. Eu acho que posso olhar no padrão. Ah, encontrei: Nota: std :: nullptr_t é um tipo distinto que não é um tipo de ponteiro nem um ponteiro para o tipo de membro; em vez disso, um pré-valor desse tipo é uma constante de ponteiro nulo e pode ser convertido em um valor de ponteiro nulo ou em um valor de ponteiro de membro nulo.nullptr
.Assim como outros já disseram, sua principal vantagem está em sobrecargas. E embora as
int
sobrecargas explícitas versus as de ponteiros possam ser raras, considere as funções padrão da biblioteca, comostd::fill
(que me incomodou mais de uma vez no C ++ 03):Não compila:
Cannot convert int to MyClass*
.fonte
A IMO é mais importante do que esses problemas de sobrecarga: em construções de modelos profundamente aninhadas, é difícil não perder o controle dos tipos e fornecer assinaturas explícitas é um grande esforço. Portanto, para tudo o que você usa, quanto mais precisamente o objetivo é o objetivo, reduzirá a necessidade de assinaturas explícitas e permitirá que o compilador produza mensagens de erro mais perspicazes quando algo der errado.
fonte