Principes de base de nullptr
std::nullptr_t
est le type du littéral de pointeur nul, nullptr. C'est une prvalue / rvalue de type std::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:
void fun(int); // two overloads of fun
void fun(void*);
fun(0); // calls f(int), not fun(void*)
fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*)
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:
fun(nullptr); // calls fun(void*) overload
Utiliser nullptr au lieu de 0 ou NULL évite ainsi les surprises de résolution de surcharge.
Un autre avantage de nullptr
sur NULL(0)
lors de l'utilisation automatique pour le type de retour
Par exemple, supposons que vous rencontriez ceci dans une base de code:
auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
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,
auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
il n'y a pas d'ambiguïté: le résultat doit être un type pointeur.
Avantage 3
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
//do something
return 0.0;
}
bool f3(int* pw) // mutex is locked
{
return 0;
}
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;
void lockAndCallF1()
{
MuxtexGuard g(f1m); // lock mutex for f1
auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
cout<< result<<endl;
}
void lockAndCallF2()
{
MuxtexGuard g(f2m); // lock mutex for f2
auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
cout<< result<<endl;
}
void lockAndCallF3()
{
MuxtexGuard g(f3m); // lock mutex for f2
auto result = f3(nullptr);// pass nullptr as null ptr to f3
cout<< result<<endl;
} // unlock mutex
int main()
{
lockAndCallF1();
lockAndCallF2();
lockAndCallF3();
return 0;
}
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èle lockAndCall
au lieu d'une définition multiple lockAndCallF1, lockAndCallF2 & lockAndCallF3
pour le code redondant.
Le code est re-factorisé comme ci-dessous:
#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
//do something
return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
//do something
return 0.0;
}
bool f3(int* pw) // mutex is locked
{
return 0;
}
std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;
template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
//decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
MuxtexGuard g(mutex);
return func(ptr);
}
int main()
{
auto result1 = lockAndCall(f1, f1m, 0); //compilation failed
//do something
auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed
//do something
auto result3 = lockAndCall(f3, f3m, nullptr);
//do something
return 0;
}
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ètre f1
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 a std::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 un std::shared_ptr<int>
.
L'analyse de l'appel impliquant NULL
est essentiellement la même. Quand NULL
est passé à lockAndCall
, un type intégral est déduit pour le paramètre ptr, et une erreur de type se produit lorsque ptr
—un type int ou de type int — est passé à f2
, qui s'attend à obtenir un std::unique_ptr<int>
.
En revanche, l'appel impliquant nullptr
n'a aucun problème. Quand nullptr
est passé à lockAndCall
, le type de ptr
est déduit comme étant std::nullptr_t
. Quand ptr
est passé à f3
, il y a une conversion implicite de std::nullptr_t
à int*
, car std::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
.
int
etvoid *
ne choisira pas laint
version sur lavoid *
version lors de l'utilisationnullptr
.