Cos’è esattamente nullptr?

Ora abbiamo C ++ 11 con molte nuove funzionalità. Uno interessante e confusionario (almeno per me) è il nuovo nullptr .

Bene, non c’è più bisogno per la ctriggers macro NULL .

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

Comunque, non capisco come funziona nullptr . Ad esempio, l’ articolo di Wikipedia dice:

C ++ 11 corregge questo introducendo una nuova parola chiave che funge da costante puntatore nullo distinto: nullptr. È di tipo nullptr_t , che è implicitamente convertibile e paragonabile a qualsiasi tipo di puntatore o tipo puntatore-membro. Non è implicitamente convertibile o paragonabile a tipi interi, ad eccezione del bool.

Come è una parola chiave e un’istanza di un tipo?

Inoltre, hai un altro esempio (oltre a quello di Wikipedia) dove nullptr è superiore al buon vecchio 0 ?

Come è una parola chiave e un’istanza di un tipo?

Questo non è sorprendente. Sia true che false sono parole chiave e come letterali hanno un tipo ( bool ). nullptr è un letterale del puntatore di tipo std::nullptr_t , ed è un valore prvalue (non puoi prendere l’indirizzo di esso usando & ).

  • 4.10 sulla conversione puntatore dice che un valore di tipo std::nullptr_t è una costante di puntatore nullo e che una costante di puntatore nullo integrale può essere convertita in std::nullptr_t . La direzione opposta non è consentita. Ciò consente di sovraccaricare una funzione per i puntatori e gli interi e il passaggio di nullptr per selezionare la versione del puntatore. Passando NULL o 0 si selezionerebbe in modo confuso la versione int .

  • Un cast di nullptr_t in un tipo integrale richiede un reinterpret_cast e ha la stessa semantica di un cast di (void*)0 in un tipo integrale (implementazione di mapping definita). Un reinterpret_cast non può convertire nullptr_t in alcun tipo di puntatore. Affidati alla conversione implicita, se ansible, oppure usa static_cast .

  • Lo standard richiede che sizeof(nullptr_t) sia sizeof(void*) .

Da nullptr: un puntatore nullo sicuro e chiaro di tipo :

La nuova parola chiave nullptr C ++ 09 designa una costante rvalue che funge da letterale del puntatore null universale, sostituendo il letterale 0 buggy e debolmente digitato e la famigerata macro NULL. nullptr mette così fine a oltre 30 anni di imbarazzo, ambiguità e bug. Le seguenti sezioni presentano la funzione nullptr e mostrano come può risolvere i disturbi di NULL e 0.

Altre referenze:

  • WikiBooks , con codice di esempio.
  • Qui a Stack Overflow: usi NULL o 0 (zero) per i puntatori in C ++?
  • template
  • Gruppo Google: comp.lang.c ++. Moderato – discussione del compilatore

Quando hai una funzione che può ricevere puntatori a più di un tipo, chiamarla con NULL è ambigua. Il modo in cui ciò viene risolto ora è molto hacky accettando un int e assumendo che sia NULL .

 template  class ptr { T* p_; public: ptr(T* p) : p_(p) {} template  ptr(U* u) : p_(dynamic_cast(u)) { } // Without this ptr p(NULL) would be ambiguous ptr(int null) : p_(NULL) { assert(null == NULL); } }; 

In C++11 potresti sovraccaricare su nullptr_t modo tale che ptr p(42); sarebbe un errore in fase di compilazione piuttosto che un’asserzione in fase di esecuzione.

 ptr(std::nullptr_t) : p_(nullptr) { } 

Perché nullptr in C ++ 11? Che cos’è? Perché NULL non è sufficiente?

L’esperto di C ++ Alex Allain lo dice perfettamente qui :

“… immagina di avere le seguenti due dichiarazioni di funzione:

 void func(int n); void func(char *s); func( NULL ); // guess which function gets called? 

Anche se sembra che verrà chiamata la seconda funzione – dopo tutto, stai passando in quello che sembra essere un puntatore – è davvero la prima funzione che verrà chiamata! Il problema è che poiché NULL è 0 e 0 è un numero intero, verrà chiamata la prima versione di func. Questo è il tipo di cosa che, sì, non accade tutto il tempo, ma quando succede, è estremamente frustrante e confusa. Se non conoscessi i dettagli di ciò che sta accadendo, potrebbe sembrare un bug del compilatore. Una funzionalità linguistica simile a un bug del compilatore è, beh, non qualcosa che desideri.

Inserisci nullptr. In C ++ 11, nullptr è una nuova parola chiave che può (e dovrebbe!) Essere utilizzata per rappresentare i puntatori NULL; in altre parole, dovunque scrivessi NULL prima, dovresti usare nullptr. Non è più chiaro per voi, il programmatore , (tutti sanno cosa significa NULL), ma è più esplicito per il compilatore , che non vedrà più gli 0 ovunque utilizzati per avere un significato speciale quando usati come puntatore. ”

nullptr non può essere assegnato a un integral type come un int ma solo un pointer tipo; o un tipo di puntatore incorporato come int *ptr o un puntatore intelligente come std::shared_ptr

Credo che questa sia un’importante distinzione perché NULL può ancora essere assegnato a un integral type ea un pointer poiché NULL è una macro espansa a 0 che può fungere sia da un valore iniziale per un int che da un pointer .

Inoltre, hai un altro esempio (oltre a quello di Wikipedia) dove nullptr è superiore al buon vecchio 0?

Sì. È anche un esempio (semplificato) del mondo reale che si è verificato nel nostro codice di produzione. Si è distinto solo perché gcc è stato in grado di emettere un avvertimento durante la compilazione incrociata su una piattaforma con larghezza del registro diversa (non sono ancora sicuro esattamente perché solo quando si esegue il crosscompiling da x86_64 a x86, avverte l’ warning: converting to non-pointer type 'int' from NULL ) :

Considera questo codice (C ++ 03):

 #include  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; } 

Produce questo risultato:

 (a == pb): 1 (a == 0): 0 (a == NULL): 0 (a == null): 1 

È una parola chiave perché lo standard la specificherà come tale. 😉 Secondo l’ultima bozza pubblica (n2914)

2.14.7 Lettori del puntatore [lex.nullptr]

 pointer-literal: nullptr 

Il letterale del puntatore è la parola chiave nullptr . È un valore di tipo std::nullptr_t .

È utile perché non converte implicitamente in un valore integrale.

Bene, altri linguaggi hanno parole riservate che sono istanze di tipi. Python, ad esempio:

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

Questo è in realtà un confronto abbastanza vicino perché None è in genere utilizzato per qualcosa che non è stato inizializzato, ma allo stesso tempo confronti come None == 0 sono falsi.

D’altra parte, in plain C, NULL == 0 restituirebbe true IIRC perché NULL è solo una macro che restituisce 0, che è sempre un indirizzo non valido (AFAIK).

Diciamo che hai una funzione (f) che è sovraccaricata per prendere sia int che char *. Prima di C ++ 11, se volevi chiamarlo con un puntatore nullo e hai usato NULL (cioè il valore 0), allora chiameresti l’overloading per int:

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

Questo probabilmente non è quello che volevi. C ++ 11 risolve questo problema con nullptr; Ora puoi scrivere quanto segue:

 void g() { f(nullptr); //calls f(char*) } 

0 era l’unico valore intero che poteva essere usato come inizializzatore senza cast per i puntatori: non è ansible inizializzare i puntatori con altri valori interi senza cast. Puoi considerare 0 come un singleton consexpr sintatticamente simile a un intero letterale. Può iniziare qualsiasi puntatore o numero intero. Ma sorprendentemente, scoprirai che non ha un tipo distinto: è un int . Quindi, come mai 0 può inizializzare i puntatori e 1 non può? Una risposta pratica era che abbiamo bisogno di un mezzo per definire il valore nullo del puntatore e la conversione implicita diretta di int a un puntatore è soggetta a errori. Così 0 divenne una vera bestia strana fuori dall’era preistorica. nullptr stato proposto di essere una rappresentazione constexpr reale singleton di valore null per inizializzare i puntatori. Non può essere utilizzato per inizializzare direttamente gli interi ed eliminare le ambiguità implicate nella definizione di NULL in termini di 0. nullptr potrebbe essere definito come una libreria che utilizza la syntax std ma semanticamente sembrava essere un componente principale mancante. NULL ora è deprecato a favore di nullptr , a meno che qualche libreria decida di definirlo come nullptr .

NULL non deve essere 0. Finché usi sempre NULL e mai 0, NULL può avere qualsiasi valore. Quando si programma un microcontrollore von Neuman con memoria piatta, che ha i suoi interrupt vektors su 0. Se NULL è 0 e qualcosa scrive su un puntatore NULL, il microcontrollore si blocca. Se NULL è diciamo 1024 e al 1024 c’è una variabile riservata, la scrittura non lo bloccherà, e puoi rilevare assegnamenti di Puntatore NULL dall’interno del programma. Questo è inutile su PC, ma per sonde spaziali, attrezzature militari o mediche è importante non schiantarsi.