Nous avons maintenant C++ 11 avec beaucoup de nouvelles fonctionnalités. Un intéressant et déroutant (du moins pour moi) est le nouveau nullptr
.
Eh bien, plus besoin de la macro NULL
.
int* x = nullptr;
myclass* obj = nullptr;
Cependant, je ne comprends pas comment nullptr
fonctionne. Par exemple, article Wikipedia dit:
C++ 11 corrige cela en introduisant un nouveau mot-clé pour servir de constante de pointeur null distinguée: nullptr. Il s'agit de type nullptr_t, qui est implicitement convertible et comparable à tout type de pointeur ou de type pointeur à membre. Il n'est pas implicitement convertible ou comparable aux types intégraux, sauf pour bool.
Comment est-ce un mot-clé et une instance d'un type?
Aussi, avez-vous un autre exemple (à côté de celui de Wikipedia) où nullptr
est supérieur au bon vieux 0
?
Comment est-ce un mot-clé et une instance d'un type?
Ce n'est pas surprenant. true
et false
sont des mots-clés et, en tant que littéraux, ils ont un type (bool
). nullptr
est un pointeur littéral de type std::nullptr_t
, et c'est une valeur (vous ne pouvez pas en prendre l'adresse à l'aide de &
).
4.10
à propos de la conversion de pointeur indique qu'une prvalue de type std::nullptr_t
est une constante de pointeur null et qu'une constante de pointeur null intégrale peut être convertie en std::nullptr_t
. La direction opposée n'est pas autorisée. Cela permet de surcharger une fonction pour les pointeurs et les entiers, et de transmettre nullptr
pour sélectionner la version du pointeur. Passer NULL
ou 0
sélectionnerait la version int
avec confusion.
Une conversion de nullptr_t
en type intégral nécessite un reinterpret_cast
et a la même sémantique qu'une transformation de (void*)0
en un type intégral (implémentation de mappage définie). Un reinterpret_cast
ne peut pas convertir nullptr_t
en n'importe quel type de pointeur. Fiez-vous si possible à la conversion implicite ou utilisez static_cast
.
La norme exige que sizeof(nullptr_t)
soit sizeof(void*)
.
De nullptr: Un pointeur nul, sûr et de type coupé :
Le nouveau mot clé C++ 09 nullptr désigne une constante rvalue qui sert de littéral pointeur null universel, remplaçant le littéral buggy et faiblement typé 0 et la fameuse macro NULL. Ainsi, nullptr met fin à plus de 30 ans d'embarras, d'ambiguïté et de bugs. Les sections suivantes présentent la fonctionnalité nullptr et montrent comment il peut remédier aux maux de NULL et de 0.
Autres références:
template
Lorsque vous avez une fonction pouvant recevoir des pointeurs sur plusieurs types, l'appel avec NULL
est ambigu. La façon dont cela fonctionne à présent est très compliquée en acceptant un int et en supposant que ce soit 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); }
};
Dans C++11
, vous pourrez surcharger nullptr_t
de sorte que ptr<T> p(42);
soit une erreur de compilation plutôt qu'une exécution assert
.
ptr(std::nullptr_t) : p_(nullptr) { }
Expert C++ Alex Allain le dit parfaitement ici :
"... imaginons que vous ayez les deux déclarations de fonction suivantes:
void func(int n);
void func(char *s);
func( NULL ); // guess which function gets called?
Bien qu'il semble que la deuxième fonction soit appelée - vous passez, après tout, dans ce qui semble être un pointeur - c'est vraiment la première fonction qui sera appelée! Le problème est que, puisque NULL est égal à 0 et que 0 est un entier, la première version de func sera appelée à la place. C'est le genre de chose qui, oui, n'arrive pas tout le temps, mais quand cela arrive, c'est extrêmement frustrant et déroutant. Si vous ne connaissez pas les détails de ce qui se passe, cela pourrait ressembler à un bogue du compilateur. Une fonctionnalité de langage qui ressemble à un bogue du compilateur n'est pas quelque chose que vous voulez.
Entrez nullptr. En C++ 11, nullptr est un nouveau mot clé qui peut (et devrait!) Être utilisé pour représenter les pointeurs NULL; en d’autres termes, où que vous écriviez NULL auparavant, vous devriez utiliser nullptr à la place. Ce n'est pas plus clair pour vous, le programmeur, (tout le monde sait ce que veut dire NULL), mais c'est plus explicite pour le compilateur, qui ne verra plus les 0 partout être utilisés pour avoir une signification spéciale lorsqu'ils sont utilisés comme un pointeur. "
nullptr
ne peut pas être affecté à un integral type
tel qu'un int mais uniquement un type pointer
; soit un type de pointeur intégré tel que int *ptr
, soit un pointeur intelligent tel que std::shared_ptr<T>
Je pense que cette distinction est importante car NULL
peut toujours être affecté à un integral type
et un pointer
comme NULL
est une macro étendue à 0
qui peut servir à la fois de valeur initiale pour un int
et de pointer
.
Aussi, avez-vous un autre exemple (à côté de celui de Wikipedia) où
nullptr
est supérieur au bon vieux 0?
Oui. C'est aussi un exemple du monde réel (simplifié) qui s'est produit dans notre code de production. Cela se démarquait uniquement parce que gcc pouvait émettre un avertissement lors de la compilation croisée sur une plate-forme avec une largeur de registre différente (vous ne savez toujours pas exactement pourquoi uniquement lors de la compilation croisée de x86_64 à x86, avertit warning: converting to non-pointer type 'int' from NULL
):
Considérons ce code (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;
}
Cela donne cette sortie:
(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1
Eh bien, d'autres langues ont des mots réservés qui sont des instances de types. Python, par exemple:
>>> None = 5
File "<stdin>", line 1
SyntaxError: assignment to None
>>> type(None)
<type 'NoneType'>
Il s'agit en fait d'une comparaison assez étroite, car None
est généralement utilisé pour quelque chose qui n'a pas été initialisé, mais dans le même temps, les comparaisons telles que None == 0
sont fausses.
D'autre part, en clair, NULL == 0
renverrait la valeur IIRC car NULL
est simplement une macro renvoyant 0, qui est toujours une adresse non valide (AFAIK).
C'est un mot clé car la norme le précisera en tant que tel. ;-) Selon le dernier projet public (n2914)
2.14.7 Littéraux de pointeur [Lex.nullptr]
pointer-literal: nullptr
Le littéral de pointeur est le mot clé
nullptr
. C'est une valeur de typestd::nullptr_t
.
C'est utile car il ne se convertit pas implicitement en une valeur intégrale.
Disons que vous avez une fonction (f) qui est surchargée pour prendre int et char *. Avant C++ 11, si vous vouliez l'appeler avec un pointeur null et que vous utilisiez NULL (c'est-à-dire la valeur 0), vous appelleriez celui surchargé pour int:
void f(int);
void f(char*);
void g()
{
f(0); // Calls f(int).
f(NULL); // Equals to f(0). Calls f(int).
}
Ce n'est probablement pas ce que tu voulais. C++ 11 résout ce problème avec nullptr; Maintenant, vous pouvez écrire ce qui suit:
void g()
{
f(nullptr); //calls f(char*)
}
0 était auparavant la seule valeur entière pouvant être utilisée en tant qu'initialiseur sans conversion pour les pointeurs: vous ne pouvez pas initialiser les pointeurs avec d'autres valeurs entières sans une conversion . Vous pouvez considérer 0 comme un singleton consexpr syntaxiquement similaire à un entier. littéral. Il peut initier n’importe quel pointeur ou entier. Mais étonnamment, vous constaterez qu’il n’a pas de type distinct: c’est un int
. Alors, comment se fait-il que 0 puisse initialiser des pointeurs et 1 ne le puisse pas? Une réponse pratique était que nous avions besoin d'un moyen de définir la valeur null du pointeur et la conversion implicite directe de int
en pointeur est sujette aux erreurs. Ainsi, 0 est devenu une véritable bête bizarre sortant de l’ère préhistorique .nullptr
a été proposé comme une véritable représentation singleton constexpr de valeur nulle pour initialiser les pointeurs. Il ne peut pas être utilisé pour initialiser directement des entiers et élimine les ambiguïtés impliquées dans la définition de NULL
en termes de 0. nullptr
pourrait être défini comme une bibliothèque utilisant la syntaxe std mais semblait sémantiquement être un composant principal manquant .NULL
est maintenant obsolète en faveur de nullptr
, sauf si une bibliothèque décide de le définir en tant que nullptr
.