Ce morceau de code fait conceptuellement la même chose pour les trois pointeurs (initialisation sûre du pointeur):
int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;
Et donc, quels sont les avantages d'attribuer des pointeurs nullptr
plutôt que de leur attribuer les valeurs NULL
ou 0
?
int
etvoid *
ne choisira pas laint
version sur lavoid *
version lors de l'utilisationnullptr
.f(nullptr)
c'est différent def(NULL)
. Mais en ce qui concerne le code ci-dessus (attribution à une variable locale), les trois pointeurs sont exactement les mêmes. Le seul avantage est la lisibilité du code.nullptr
la différence entre 0 etNULL
Réponses:
Dans ce code, il ne semble pas y avoir d'avantage. Mais considérez les fonctions surchargées suivantes:
Quelle fonction sera appelée? Bien sûr, l'intention ici est d'appeler
f(char const *)
, mais en réalitéf(int)
sera appelée! C'est un gros problème 1 , n'est-ce pas?Donc, la solution à de tels problèmes est d'utiliser
nullptr
:Bien sûr, ce n'est pas le seul avantage de
nullptr
. En voici une autre:Depuis dans le modèle, le type de
nullptr
est déduit commenullptr_t
, vous pouvez donc écrire ceci:1. En C ++,
NULL
est défini comme#define NULL 0
, donc c'est fondamentalementint
, c'est pourquoi ilf(int)
est appelé.la source
nullptr
? (Non, je ne suis pas exigeant)NULL
est requis par la norme pour avoir un type intégral, et c'est pourquoi il est généralement défini comme0
ou0L
. De plus, je ne suis pas sûr d'aimer cettenullptr_t
surcharge, car elle n'attrape que les appels avecnullptr
, pas avec un pointeur nul d'un type différent, comme(void*)0
. Mais je peux croire qu'il a quelques utilisations, même si tout ce qu'il fait est de vous éviter de définir votre propre type d'espace réservé à valeur unique pour signifier «aucun».nullptr
a une valeur numérique bien définie, contrairement aux constantes de pointeur nul. Une constante de pointeur nul est convertie en pointeur nul de ce type (quel qu'il soit). Il est nécessaire que deux pointeurs nuls du même type se comparent de manière identique, et la conversion booléenne transforme un pointeur nul enfalse
. Rien d'autre n'est requis. Par conséquent, il est possible pour un compilateur (idiot, mais possible) d'utiliser par exemple0xabcdef1234
ou un autre nombre pour le pointeur nul. D'autre part,nullptr
est nécessaire pour convertir en zéro numérique.f(nullptr)
n'appellera pas la fonction prévue? Il y avait plus d'une motivation. Beaucoup d'autres choses utiles peuvent être découvertes par les programmeurs eux-mêmes dans les années à venir. Vous ne pouvez donc pas dire qu'il n'y a qu'un seul véritable usage denullptr
.C ++ 11 introduit
nullptr
, il est connu comme laNull
constante de pointeur et améliore la sécurité des types et résout les situations ambiguës contrairement à la constante de pointeur nul dépendante de l'implémentation existanteNULL
. Pour pouvoir comprendre les avantages denullptr
. nous devons d'abord comprendre ce qu'estNULL
et quels sont les problèmes qui y sont associés.Qu'est-ce que c'est
NULL
exactement?Le pré C ++ 11
NULL
était utilisé pour représenter un pointeur sans valeur ou un pointeur qui ne pointe vers rien de valide. Contrairement à la notion courante, ilNULL
n'y a pas de mot-clé en C ++ . Il s'agit d'un identifiant défini dans les en-têtes de bibliothèque standard. En bref, vous ne pouvez pas utiliserNULL
sans inclure certains en-têtes de bibliothèque standard. Considérez le programme exemple :Production:
La norme C ++ définit NULL comme une macro définie par l'implémentation définie dans certains fichiers d'en-tête de bibliothèque standard. L'origine de NULL est de C et C ++ l'a hérité de C. Le standard C défini NULL comme
0
ou(void *)0
. Mais en C ++, il y a une différence subtile.C ++ n'a pas pu accepter cette spécification telle quelle. Contrairement à C, C ++ est un langage fortement typé (C ne nécessite pas de conversion explicite de
void*
vers n'importe quel type, tandis que C ++ impose une conversion explicite). Cela rend la définition de NULL spécifiée par la norme C inutile dans de nombreuses expressions C ++. Par exemple:Si NULL était défini comme
(void *)0
, aucune des expressions ci-dessus ne fonctionnerait.void *
àstd::string
.void *
vers pointeur vers une fonction membre est nécessaire.Donc, contrairement à C, C ++ Standard mandaté pour définir NULL comme littéral numérique
0
ou0L
.Alors, quel est le besoin d'une autre constante de pointeur nul alors que nous l'avons
NULL
déjà?Bien que le comité des normes C ++ ait proposé une définition NULL qui fonctionnera pour C ++, cette définition avait sa propre part de problèmes. NULL fonctionnait assez bien pour presque tous les scénarios mais pas tous. Il a donné des résultats surprenants et erronés pour certains scénarios rares. Par exemple :
Production:
Clairement, l'intention semble être d'appeler la version qui prend
char*
comme argument, mais comme la sortie le montre, la fonction qui prend uneint
version est appelée. En effet, NULL est un littéral numérique.De plus, comme il est défini par l'implémentation si NULL est 0 ou 0L, il peut y avoir beaucoup de confusion dans la résolution de surcharge de fonction.
Exemple de programme:
Analyse de l'extrait de code ci-dessus:
doSomething(char *)
comme prévu.doSomething(int)
mais peut-être lachar*
version était-elle souhaitée car0
EST également un pointeur nul.NULL
est défini comme0
, appelledoSomething(int)
quand peut-êtredoSomething(char *)
était prévu, ce qui peut entraîner une erreur de logique lors de l'exécution. SiNULL
est défini comme0L
, l'appel est ambigu et entraîne une erreur de compilation.Ainsi, en fonction de la mise en œuvre, le même code peut donner divers résultats, ce qui est clairement indésirable. Naturellement, le comité des normes C ++ a voulu corriger cela et c'est la principale motivation de nullptr.
Alors qu'est-ce que c'est
nullptr
et comment évite-t-il les problèmes deNULL
?C ++ 11 introduit un nouveau mot clé
nullptr
pour servir de constante de pointeur nul. Contrairement à NULL, son comportement n'est pas défini par l'implémentation. Ce n'est pas une macro mais elle a son propre type. nullptr a le typestd::nullptr_t
. C ++ 11 définit de manière appropriée les propriétés du nullptr afin d'éviter les inconvénients de NULL. Pour résumer ses propriétés:Propriété 1: il a son propre type
std::nullptr_t
, etPropriété 2: il est implicitement convertible et comparable à tout type de pointeur ou type de pointeur vers membre, mais
Propriété 3: il n'est pas implicitement convertible ou comparable à des types intégraux, sauf pour
bool
.Prenons l'exemple suivant:
Dans le programme ci-dessus,
char *
Version des appels , propriétés 2 et 3Ainsi l'introduction de nullptr évite tous les problèmes du bon vieux NULL.
Comment et où utiliser
nullptr
?La règle d'or pour C ++ 11 est simplement de commencer à utiliser
nullptr
chaque fois que vous auriez autrement utilisé NULL dans le passé.Références standard:
C ++ 11 Standard: C.3.2.4 Macro NULL
C ++ 11 Standard: 18.2 Types
C ++ 11 Standard: 4.10 Conversions de pointeur
C99 Standard: 6.3.2.3 Pointeurs
la source
nullptr
, bien que je ne sache pas quelle différence cela fait vraiment à mon code. Merci pour la bonne réponse et surtout pour l'effort. M'a apporté beaucoup de lumière sur le sujet.0xccccc....
, mais, une variable sans valeur est une contradiction inhérente.bool flag = nullptr;
). Non, pas OK, j'obtiens l'erreur suivante au moment de la compilation avec g ++ 6:error: converting to ‘bool’ from ‘std::nullptr_t’ requires direct-initialization [-fpermissive]
La vraie motivation ici est une transmission parfaite .
Considérer:
En termes simples, 0 est une valeur spéciale , mais les valeurs ne peuvent pas se propager à travers le système, seuls les types le peuvent. Les fonctions de transfert sont essentielles et 0 ne peut pas les gérer. Ainsi, il fallait absolument introduire
nullptr
, où le type est ce qui est spécial, et le type peut effectivement se propager. En fait, l'équipe MSVC a dû se présenter plusnullptr
tôt que prévu après avoir implémenté des références rvalue, puis découvert cet écueil par elle-même.Il y a quelques autres cas de coin où
nullptr
peuvent rendre la vie plus facile - mais ce n'est pas un cas de base, car un casting peut résoudre ces problèmes. ConsidérerAppelle deux surcharges distinctes. De plus, considérez
C'est ambigu. Mais, avec nullptr, vous pouvez fournir
la source
forward((int*)0)
travaux. Est-ce que je manque quelque chose?Principes de base de nullptr
std::nullptr_t
est le type du littéral de pointeur nul, nullptr. C'est une prvalue / rvalue de typestd::nullptr_t
. Il existe des conversions implicites de nullptr en valeur de pointeur null de tout type de pointeur.Le littéral 0 est un int, pas un pointeur. Si C ++ se retrouve à regarder 0 dans un contexte où seul un pointeur peut être utilisé, il interprétera à contrecœur 0 comme un pointeur nul, mais c'est une position de repli. La politique principale de C ++ est que 0 est un int, pas un pointeur.
Avantage 1 - Supprimez l'ambiguïté lors de la surcharge sur les types pointeur et intégrale
En C ++ 98, la principale implication de ceci était que la surcharge sur les types pointeur et intégrale pouvait conduire à des surprises. Passer 0 ou NULL à de telles surcharges n'a jamais appelé une surcharge de pointeur:
La chose intéressante à propos de cet appel est la contradiction entre la signification apparente du code source («j'appelle fun avec NULL-le pointeur nul») et sa signification réelle («j'appelle fun avec une sorte d'entier - pas le null aiguille").
L'avantage de nullptr est qu'il n'a pas de type intégral. L'appel de la fonction surchargée fun avec nullptr appelle la surcharge void * (c'est-à-dire la surcharge du pointeur), car nullptr ne peut pas être considéré comme une intégrale:
Utiliser nullptr au lieu de 0 ou NULL évite ainsi les surprises de résolution de surcharge.
Un autre avantage de
nullptr
surNULL(0)
lors de l'utilisation automatique pour le type de retourPar exemple, supposons que vous rencontriez ceci dans une base de code:
Si vous ne savez pas (ou ne pouvez pas trouver facilement) ce que findRecord retourne, il peut ne pas être clair si result est un type pointeur ou un type intégral. Après tout, 0 (quel résultat est testé) pourrait aller dans les deux sens. Si vous voyez ce qui suit, en revanche,
il n'y a pas d'ambiguïté: le résultat doit être un type pointeur.
Avantage 3
Le programme ci-dessus est compilé et exécuté avec succès mais lockAndCallF1, lockAndCallF2 et lockAndCallF3 ont un code redondant. Il est dommage d'écrire un code comme celui-ci si nous pouvons écrire un modèle pour tout cela
lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Ainsi, il peut être généralisé avec un modèle. J'ai écrit une fonction de modèlelockAndCall
au lieu d'une définition multiplelockAndCallF1, lockAndCallF2 & lockAndCallF3
pour le code redondant.Le code est re-factorisé comme ci-dessous:
Analyse détaillée pourquoi la compilation a échoué pour
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
non pourlockAndCall(f3, f3m, nullptr)
Pourquoi la compilation a
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
échoué?Le problème est que lorsque 0 est passé à lockAndCall, la déduction du type de modèle intervient pour déterminer son type. Le type de 0 est int, c'est donc le type du paramètre ptr à l'intérieur de l'instanciation de cet appel à lockAndCall. Malheureusement, cela signifie que dans l'appel à func à l'intérieur de lockAndCall, un int est passé, et ce n'est pas compatible avec le
std::shared_ptr<int>
paramètref1
attendu. Le 0 passé dans l'appel àlockAndCall
était destiné à représenter un pointeur nul, mais ce qui était réellement passé était int. Essayer de passer cet int à f1 comme astd::shared_ptr<int>
est une erreur de type. L'appel àlockAndCall
avec 0 échoue car à l'intérieur du modèle, un int est passé à une fonction qui nécessite unstd::shared_ptr<int>
.L'analyse de l'appel impliquant
NULL
est essentiellement la même. QuandNULL
est passé àlockAndCall
, un type intégral est déduit pour le paramètre ptr, et une erreur de type se produit lorsqueptr
—un type int ou de type int — est passé àf2
, qui s'attend à obtenir unstd::unique_ptr<int>
.En revanche, l'appel impliquant
nullptr
n'a aucun problème. Quandnullptr
est passé àlockAndCall
, le type deptr
est déduit comme étantstd::nullptr_t
. Quandptr
est passé àf3
, il y a une conversion implicite destd::nullptr_t
àint*
, carstd::nullptr_t
convertit implicitement en tous les types de pointeurs.Il est recommandé, chaque fois que vous voulez faire référence à un pointeur nul, utilisez nullptr, pas 0 ou
NULL
.la source
Il n'y a aucun avantage direct à avoir
nullptr
de la manière dont vous avez montré les exemples.Mais considérez une situation où vous avez 2 fonctions avec le même nom; 1 prend
int
et un autre unint*
Si vous voulez appeler
foo(int*)
en passant un NULL, alors le chemin est:nullptr
le rend plus simple et intuitif :Lien supplémentaire de la page Web de Bjarne.
Non pertinent mais sur la note latérale de C ++ 11:
la source
decltype(nullptr)
eststd::nullptr_t
.typedef decltype(nullptr) nullptr_t;
. Je suppose que je peux regarder dans la norme. Ah, je l' ainullptr
.Comme d'autres l'ont déjà dit, son principal avantage réside dans les surcharges. Et bien que les
int
surcharges explicites par rapport aux pointeurs puissent être rares, considérez les fonctions de bibliothèque standard commestd::fill
(qui m'a mordu plus d'une fois en C ++ 03):Ne compile pas:
Cannot convert int to MyClass*
.la source
IMO est plus important que ces problèmes de surcharge: dans les constructions de modèles profondément imbriquées, il est difficile de ne pas perdre la trace des types, et donner des signatures explicites est tout un effort. Donc, pour tout ce que vous utilisez, le plus précisément axé sur l'objectif visé, mieux c'est, cela réduira le besoin de signatures explicites et permettra au compilateur de produire des messages d'erreur plus perspicaces en cas de problème.
la source