O que exatamente é nullptr?

570

Agora temos o C ++ 11 com muitos novos recursos. Um interessante e confuso (pelo menos para mim) é o novo nullptr.

Bem, não há mais necessidade da macro desagradável NULL.

int* x = nullptr;
myclass* obj = nullptr;

Ainda assim, não estou entendendo como nullptrfunciona. Por exemplo, o artigo da Wikipedia diz:

O C ++ 11 corrige isso introduzindo uma nova palavra - chave para servir como uma constante distinta de ponteiro nulo: nullptr. É do tipo nullptr_t , implicitamente conversível e comparável a qualquer tipo de ponteiro ou tipo de ponteiro para membro. Não é implicitamente conversível ou comparável a tipos integrais, exceto bool.

Como é uma palavra-chave e uma instância de um tipo?

Além disso, você tem outro exemplo (além do da Wikipedia) em que nullptré superior ao bom e velho 0?

AraK
fonte
23
fato relacionado: nullptrtambém é usado para representar referência nula para identificadores gerenciados em C ++ / CLI.
Mehrdad Afshari
3
Ao usar o Visual C ++, lembre-se de que, se você usar nullptr com código C / C ++ nativo e compilar com a opção de compilador / clr, o compilador não poderá determinar se nullptr indica um valor de ponteiro nulo gerenciado ou nativo. Para deixar sua intenção clara para o compilador, use nullptr para especificar um valor gerenciado ou __nullptr para especificar um valor nativo. A Microsoft implementou isso como uma extensão de componente.
cseder
6
É nullptr_tgarantido ter apenas um membro nullptr? Portanto, se uma função retornou nullptr_t, o compilador já sabe qual valor será retornado, independentemente do corpo da função?
Aaron McDaid
8
@AaronMcDaid std::nullptr_tpode ser instanciado, mas todas as instâncias serão idênticas nullptrporque o tipo é definido como typedef decltype(nullptr) nullptr_t. Eu acredito que a principal razão pela qual o tipo existe é para que as funções possam ser sobrecarregadas especificamente para captura nullptr, se necessário. Veja aqui um exemplo.
Justin Time - Restabelece Monica
5
0 nunca foi um ponteiro nulo, ponteiro nulo é um ponteiro que pode ser obtido convertendo zero literal para o tipo de ponteiro e não aponta para nenhum objeto existente por definição.
Swift - sexta-feira,

Respostas:

403

Como é uma palavra-chave e uma instância de um tipo?

Isso não é surpreendente. Ambos truee falsesão palavras-chave e como literais eles têm um tipo ( bool). nullptré um ponteiro literal do tipo std::nullptr_te é um pré-valor (você não pode usar o endereço dele &).

  • 4.10sobre a conversão de ponteiro diz que um pré-valor do tipo std::nullptr_té uma constante de ponteiro nulo e que uma constante de ponteiro nulo integral pode ser convertida em std::nullptr_t. A direção oposta não é permitida. Isso permite sobrecarregar uma função para ponteiros e números inteiros e passar nullptrpara selecionar a versão do ponteiro. Passar NULLou 0selecionar de forma confusa a intversão.

  • Uma conversão de nullptr_tpara um tipo integral precisa de reinterpret_castae tem a mesma semântica que uma conversão de (void*)0para um tipo integral (implementação de mapeamento definida). A reinterpret_castnão pode ser convertido nullptr_tem nenhum tipo de ponteiro. Confie na conversão implícita, se possível ou use static_cast.

  • A Norma exige que sizeof(nullptr_t)seja sizeof(void*).

Johannes Schaub - litb
fonte
Ah, depois de olhar, parece-me que o operador condicional não pode converter 0 em nullptr em casos como cond ? nullptr : 0;. Removido da minha resposta.
Johannes Schaub - litb
88
Note que NULLnem é garantido que seja 0. Pode ser 0L, nesse caso, uma chamada para void f(int); void f(char *);será ambígua. nullptrsempre favorecerá a versão do ponteiro e nunca a chamará int. Observe também que nullptr é conversível em bool(o rascunho diz isso em 4.12).
Johannes Schaub - litb 15/08/09
@ litb: então, em relação a f (int) ef (void *) - f (0) ainda será ambíguo?
911 Steve Folly
27
@ Steve, não, isso chamará a intversão. Mas f(0L)é ambíguo, porque long -> inttambém long -> void*é igualmente caro. Portanto, se NULL estiver 0Lno seu compilador, uma chamada f(NULL)será ambígua, dadas essas duas funções. Não é assim, é nullptrclaro.
Johannes Schaub - litb
2
@SvenS Não deve ser definido como (void*)0em C ++. Mas pode ser definido como qualquer constante arbitrária de ponteiro nulo, que qualquer constante integral com valor 0 e nullptrpreencha. Então, definitivamente não vai, mas pode . (Você se esqueceu de mim o ping btw ..)
Deduplicator
60

From nullptr: Um ponteiro nulo seguro e de tipo claro :

A nova palavra-chave C ++ 09 nullptr designa uma constante rvalue que serve como um literal de ponteiro nulo universal, substituindo o literal 0 de buggy e literal de tipo fraco e a infame macro NULL. Assim, o nullptr põe fim a mais de 30 anos de vergonha, ambiguidade e erros. As seções a seguir apresentam o recurso nullptr e mostram como ele pode remediar as doenças NULL e 0.

Outras referências:

nik
fonte
17
C ++ 09? Não era conhecido como C ++ 0x antes de agosto de 2011?
Michael Dorst 12/03/2013
2
@anthropomorphic Bem, esse é o seu propósito. O C ++ 0x foi usado enquanto ainda estava em andamento, porque não se sabia se seria concluído em 2008 ou 2009. Observe que ele realmente se tornou C ++ 0B, ​​significando C ++ 11. Veja stroustrup.com/C++11FAQ.html
mxmlnkn
45

Por que nullptr em C ++ 11? O que é isso? Por que NULL não é suficiente?

Alex Allain, especialista em C ++, diz isso perfeitamente aqui (minha ênfase foi adicionada em negrito):

... imagine que você tem as duas declarações de função a seguir:

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

Embora pareça que a segunda função será chamada - você está, afinal, passando o que parece ser um ponteiro - é realmente a primeira função que será chamada! O problema é que, como NULL é 0 e 0 é um número inteiro, a primeira versão do func será chamada. Esse é o tipo de coisa que, sim, não acontece o tempo todo, mas quando acontece, é extremamente frustrante e confusa. Se você não souber os detalhes do que está acontecendo, pode parecer um bug do compilador. Um recurso de linguagem que se parece com um bug do compilador não é algo que você deseja.

Digite nullptr. No C ++ 11, nullptr é uma nova palavra-chave que pode (e deve!) Ser usada para representar ponteiros NULL; em outras palavras, onde quer que você estivesse escrevendo NULL antes, você deveria usar o nullptr. Não está mais claro para você, programador , (todo mundo sabe o que NULL significa), mas é mais explícito para o compilador , que não verá mais 0s em todos os lugares sendo usados ​​para ter um significado especial quando usado como ponteiro.

Allain termina seu artigo com:

Independentemente de tudo isso - a regra de ouro para o C ++ 11 é simplesmente começar a usar nullptrsempre que você usaria NULLno passado.

(Minhas palavras):

Por fim, não esqueça que nullptré um objeto - uma classe. Ele pode ser usado em qualquer lugar que NULLfoi usado antes, mas se você precisar do tipo por algum motivo, ele pode ser extraído com decltype(nullptr), ou diretamente descrito como std::nullptr_t, o que é simplesmente um typedefdos decltype(nullptr).

Referências:

  1. Cprogramming.com: Melhores tipos em C ++ 11 - nullptr, classes enum (enumerações fortemente tipadas) e cstdint
  2. https://en.cppreference.com/w/cpp/language/decltype
  3. https://en.cppreference.com/w/cpp/types/nullptr_t
Gabriel Staples
fonte
2
Devo dizer que sua resposta é subestimada, foi muito fácil de entender através do seu exemplo.
MSS
37

Quando você tem uma função que pode receber ponteiros para mais de um tipo, chamá-la com NULLé ambígua. A maneira como isso é resolvido agora é muito hacky, aceitando um int e assumindo que é NULL.

template <class T>
class ptr {
    T* p_;
    public:
        ptr(T* p) : p_(p) {}

        template <class U>
        ptr(U* u) : p_(dynamic_cast<T*>(u)) { }

        // Without this ptr<T> p(NULL) would be ambiguous
        ptr(int null) : p_(NULL)  { assert(null == NULL); }
};

Em C++11que você seria capaz de sobrecarregar em nullptr_tmodo que ptr<T> p(42);seria um erro tempo de compilação em vez de um tempo de execução assert.

ptr(std::nullptr_t) : p_(nullptr)  {  }
Motti
fonte
E se NULLfor definido como 0L?
LF
9

nullptrnão pode ser atribuído a um tipo integral, como um intmas apenas um tipo de ponteiro; um tipo de ponteiro embutido, como int *ptrum ponteiro inteligente, comostd::shared_ptr<T>

Acredito que essa seja uma distinção importante, porque NULLainda pode ser atribuída a um tipo integral e a um ponteiro, pois NULLé uma macro expandida à 0qual pode servir tanto como um valor inicial para um intquanto para um ponteiro.

user633658
fonte
Observe que esta resposta está errada. NULLnão é garantido que seja expandido para 0.
LF
6

Além disso, você tem outro exemplo (além do da Wikipedia) em que nullptré superior ao bom e velho 0?

Sim. É também um exemplo do mundo real (simplificado) que ocorreu em nosso código de produção. Ele se destacou apenas porque o gcc foi capaz de emitir um aviso ao fazer a compilação cruzada em uma plataforma com largura de registro diferente (ainda não se sabe exatamente por que apenas a compilação cruzada de x86_64 para x86, avisa warning: converting to non-pointer type 'int' from NULL):

Considere este código (C ++ 03):

#include <iostream>

struct B {};

struct A
{
    operator B*() {return 0;}
    operator bool() {return true;}
};

int main()
{
    A a;
    B* pb = 0;
    typedef void* null_ptr_t;
    null_ptr_t null = 0;

    std::cout << "(a == pb): " << (a == pb) << std::endl;
    std::cout << "(a == 0): " << (a == 0) << std::endl; // no warning
    std::cout << "(a == NULL): " << (a == NULL) << std::endl; // warns sometimes
    std::cout << "(a == null): " << (a == null) << std::endl;
}

Rende esta saída:

(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1
Gabriel Schreiber
fonte
Não vejo como isso melhora ao usar o nullptr (e o C ++ 11). Se você definir pb como nullptr, a primeira comparação será avaliada como verdadeira (ao comparar maçãs com peras ..). O segundo caso é ainda pior: se você comparar a para nullptr, ele converterá a em B * e será avaliado como true novamente (antes de ser convertido em bool e o expr avaliado em false). Toda a coisinha me lembra de JavaScript e me pergunto se vamos obter === em C ++ no futuro :(
Nils
5

Bem, outros idiomas têm palavras reservadas que são instâncias de tipos. Python, por exemplo:

>>> None = 5
  File "<stdin>", line 1
SyntaxError: assignment to None
>>> type(None)
<type 'NoneType'>

Na verdade, é uma comparação bastante próxima, porque Nonenormalmente é usada para algo que não foi inicializado, mas ao mesmo tempo comparações como None == 0falsas.

Por outro lado, na planície C, NULL == 0retornaria o IIRC verdadeiro porque NULLé apenas uma macro retornando 0, que é sempre um endereço inválido (AFAIK).

Mark Rushakoff
fonte
4
NULLé uma macro que se expande para zero, uma conversão de zero constante para um ponteiro produz um ponteiro nulo. Um ponteiro nulo não precisa ser zero (mas geralmente é), zero nem sempre é um endereço inválido e uma conversão de zero não constante em um ponteiro não precisa ser nula, e um ponteiro nulo em uma número inteiro não precisa ser zero. Espero ter acertado tudo isso sem esquecer nada. Uma referência: c-faq.com/null/null2.html
Samuel Edwin Ward
3

É uma palavra-chave porque o padrão a especificará como tal. ;-) De acordo com o último rascunho público (n2914)

2.14.7 Literais de ponteiro [lex.nullptr]

pointer-literal:
nullptr

O ponteiro literal é a palavra-chave nullptr. É um rvalue do tipo std::nullptr_t.

É útil porque não converte implicitamente em um valor integral.

KTC
fonte
2

Digamos que você tenha uma função (f) sobrecarregada para receber int e char *. Antes do C ++ 11, se você quisesse chamá-lo com um ponteiro nulo e utilizasse NULL (ou seja, o valor 0), chamaria aquele sobrecarregado para int:

void f(int);
void f(char*);

void g() 
{
  f(0); // Calls f(int).
  f(NULL); // Equals to f(0). Calls f(int).
}

Provavelmente não é isso que você queria. C ++ 11 resolve isso com nullptr; Agora você pode escrever o seguinte:

void g()
{
  f(nullptr); //calls f(char*)
}
Amit G.
fonte
1

Deixe-me primeiro dar uma implementação não sofisticada nullptr_t

struct nullptr_t 
{
    void operator&() const = delete;  // Can't take address of nullptr

    template<class T>
    inline operator T*() const { return 0; }

    template<class C, class T>
    inline operator T C::*() const { return 0; }
};

nullptr_t nullptr;

nullptré um exemplo sutil do idioma Return Type Resolver para deduzir automaticamente um ponteiro nulo do tipo correto, dependendo do tipo de instância à qual está sendo atribuído.

int *ptr = nullptr;                // OK
void (C::*method_ptr)() = nullptr; // OK
  • Como você pode acima, quando nullptrestá sendo atribuído a um ponteiro inteiro, inté criada uma instanciação de tipo da função de conversão de modelo. E o mesmo vale para ponteiros de método também.
  • Dessa forma, aproveitando a funcionalidade do modelo, na verdade estamos criando o tipo apropriado de ponteiro nulo toda vez que fazemos, uma nova atribuição de tipo.
  • Como nullptré um literal inteiro com valor zero, você não pode usar o endereço que realizamos ao excluir & operator.

Por que precisamos nullptrem primeiro lugar?

  • Você vê que o tradicional NULLtem algum problema, como abaixo:

Conversão implícita

char *str = NULL; // Implicit conversion from void * to char *
int i = NULL;     // OK, but `i` is not pointer type

2️⃣ Função chamada ambiguidade

void func(int) {}
void func(int*){}
void func(bool){}

func(NULL);     // Which one to call?
  • Compilação produz o seguinte erro:
error: call to 'func' is ambiguous
    func(NULL);
    ^~~~
note: candidate function void func(bool){}
                              ^
note: candidate function void func(int*){}
                              ^
note: candidate function void func(int){}
                              ^
1 error generated.
compiler exit status 1

3️⃣ Sobrecarga de construtor

struct String
{
    String(uint32_t)    {   /* size of string */    }
    String(const char*) {       /* string */        }
};

String s1( NULL );
String s2( 5 );
  • Nesses casos, você precisa de uma conversão explícita (ou seja  String s((char*)0)),.
Vishal Chovatiya
fonte
0

0 costumava ser o único valor inteiro que poderia ser usado como um inicializador sem conversão para ponteiros: não é possível inicializar ponteiros com outros valores inteiros sem uma conversão. Você pode considerar 0 como um singleton consexpr sintaticamente semelhante a um literal inteiro. Pode iniciar qualquer ponteiro ou número inteiro. Mas, surpreendentemente, você descobrirá que não possui um tipo distinto: é um int. Então, como 0 pode inicializar ponteiros e 1 não? Uma resposta prática foi que precisamos de um meio de definir o valor nulo do ponteiro e a conversão implícita direta de intum ponteiro é suscetível a erros. Assim, 0 tornou-se uma verdadeira fera esquisita da era pré-histórica. nullptrfoi proposto para ser uma representação constexpr real de valor nulo para inicializar ponteiros. Ele não pode ser usado para inicializar números inteiros diretamente e eliminar as ambiguidades envolvidas na definição. Pode ser definido como uma biblioteca usando a sintaxe std, mas semântica parecia ser um componente principal ausente. agora está obsoleto em favor de , a menos que alguma biblioteca decida defini-la como .NULL na em termos de 0.nullptrNULLnullptrnullptr

Red.Wave
fonte
-1

Aqui está o cabeçalho do LLVM.

// -*- C++ -*-
//===--------------------------- __nullptr --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_NULLPTR
#define _LIBCPP_NULLPTR

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#ifdef _LIBCPP_HAS_NO_NULLPTR

_LIBCPP_BEGIN_NAMESPACE_STD

struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
    void* __lx;

    struct __nat {int __for_bool_;};

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}

    template <class _Tp>
        _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
        operator _Tp* () const {return 0;}

    template <class _Tp, class _Up>
        _LIBCPP_INLINE_VISIBILITY
        operator _Tp _Up::* () const {return 0;}

    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};

inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}

#define nullptr _VSTD::__get_nullptr_t()

_LIBCPP_END_NAMESPACE_STD

#else  // _LIBCPP_HAS_NO_NULLPTR

namespace std
{
    typedef decltype(nullptr) nullptr_t;
}

#endif  // _LIBCPP_HAS_NO_NULLPTR

#endif  // _LIBCPP_NULLPTR

(muito pode ser descoberto rapidamente grep -r /usr/include/*`)

Uma coisa que salta é a *sobrecarga do operador (retornar 0 é muito mais amigável do que segfaulting ...). Outra coisa é que não parece compatível com o armazenamento de um endereço em tudo . O que, comparado com a maneira como ele lança os vazios * e passa os resultados NULL para ponteiros normais como valores sentinela, obviamente reduziria o fator "nunca esqueça, pode ser uma bomba".

lk
fonte
-2

NULL não precisa ser 0. Desde que você use sempre NULL e nunca 0, NULL pode ser qualquer valor. Considerando que você programa um microcontrolador von Neuman com memória plana, que tem seus interruptores em 0. Se NULL for 0 e algo gravar em um ponteiro NULL, o microcontrolador trava. Se NULL for digamos 1024 e em 1024 houver uma variável reservada, a gravação não causará um travamento e você poderá detectar as atribuições NULL Pointer de dentro do programa. Isso não faz sentido nos PCs, mas para sondas espaciais, equipamentos militares ou médicos, é importante não colidir.

Axel Schweiß
fonte
2
Bem, o valor real do ponteiro nulo na memória pode não ser zero, mas o padrão C (e C ++) exige que os compiladores convertam o integral 0 literal em ponteiro nulo.
bzim