La plupart des réponses ici ne parviennent pas à résoudre l'ambiguïté inhérente à la présence d'un pointeur brut dans une signature de fonction, en termes d'expression de l'intention. Les problèmes sont les suivants:
L'appelant ne sait pas si le pointeur pointe vers un seul objet ou vers le début d'un "tableau" d'objets.
L'appelant ne sait pas si le pointeur "possède" la mémoire vers laquelle il pointe. IE, que la fonction libère ou non la mémoire. ( foo(new int)
- Est-ce une fuite de mémoire?).
L'appelant ne sait pas s'il nullptr
peut ou non être transmis en toute sécurité à la fonction.
Tous ces problèmes sont résolus par des références:
Les références se réfèrent toujours à un seul objet.
Les références ne possèdent jamais la mémoire à laquelle elles se réfèrent, elles ne sont qu'une vue dans la mémoire.
Les références ne peuvent pas être nulles.
Cela fait des références un bien meilleur candidat pour une utilisation générale. Cependant, les références ne sont pas parfaites - il y a quelques problèmes majeurs à considérer.
- Pas d'indirection explicite. Ce n'est pas un problème avec un pointeur brut, car nous devons utiliser l'
&
opérateur pour montrer que nous passons bien un pointeur. Par exemple, int a = 5; foo(a);
il n'est pas du tout clair ici que a est passé par référence et pourrait être modifié.
- Nullabilité. Cette faiblesse des pointeurs peut également être une force, lorsque nous voulons réellement que nos références soient annulables. Étant donné que ce
std::optional<T&>
n'est pas valide (pour de bonnes raisons), les pointeurs nous donnent la nullité que vous souhaitez.
Il semble donc que lorsque nous voulons une référence nullable avec une indirection explicite, nous devrions atteindre un T*
droit? Faux!
Abstractions
Dans notre désespoir de nullité, nous pouvons atteindre T*
et simplement ignorer toutes les lacunes et l'ambiguïté sémantique énumérées précédemment. Au lieu de cela, nous devrions rechercher ce que C ++ fait le mieux: une abstraction. Si nous écrivons simplement une classe qui entoure un pointeur, nous gagnons l'expressivité, ainsi que la nullité et l'indirection explicite.
template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}
T& get() const {
return *ptr;
}
explicit operator bool() const {
return bool(ptr);
}
private:
T* ptr;
};
C'est l'interface la plus simple que j'ai pu trouver, mais elle fait le travail efficacement. Il permet d'initialiser la référence, de vérifier si une valeur existe et d'accéder à la valeur. On peut l'utiliser comme ça:
void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}
int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
Nous avons atteint nos objectifs! Voyons maintenant les avantages, par rapport au pointeur brut.
- L'interface montre clairement que la référence ne doit faire référence qu'à un seul objet.
- De toute évidence, il ne possède pas la mémoire à laquelle il se réfère, car il n'a pas de destructeur défini par l'utilisateur et aucune méthode pour supprimer la mémoire.
- L'appelant sait qu'il
nullptr
peut être transmis, car l'auteur de la fonction demande explicitement unoptional_ref
Nous pourrions rendre l'interface plus complexe à partir d'ici, comme l'ajout d'opérateurs d'égalité, une interface monadique get_or
et map
, une méthode qui obtient la valeur ou lève une exception,constexpr
support. Vous pouvez le faire.
En conclusion, au lieu d'utiliser des pointeurs bruts, expliquez ce que ces pointeurs signifient réellement dans votre code, et tirez parti d'une abstraction de bibliothèque standard ou écrivez la vôtre. Cela améliorera considérablement votre code.
new
de créer un pointeur et les problèmes de propriété qui en résultent.