J'ai lu dans quelques articles que les pointeurs bruts ne devraient presque jamais être utilisés. Au lieu de cela, ils doivent toujours être placés dans des pointeurs intelligents, qu’il s’agisse de pointeurs étendus ou partagés.
Cependant, j'ai remarqué que des frameworks tels que Qt, wxWidgets et des bibliothèques telles que Boost ne retournent jamais, ni n'attendent des pointeurs intelligents, comme s'ils ne les utilisaient pas du tout. Au lieu de cela, ils retournent ou attendent des pointeurs bruts. Y a-t-il une raison à cela? Devrais-je rester à l'écart des pointeurs intelligents lorsque j'écris une API publique et pourquoi?
Je me demande simplement pourquoi les pointeurs intelligents sont recommandés alors que de nombreux grands projets semblent les éviter.
Outre le fait que de nombreuses bibliothèques ont été écrites avant l'avènement des pointeurs intelligents standard, la principale raison est probablement l'absence d'une interface ABI (C++ Application Binary Interface) standard.
Si vous écrivez une bibliothèque contenant uniquement des en-têtes, vous pouvez transmettre au contenu de votre cœur les pointeurs intelligents et les conteneurs standard. Leur source est disponible pour votre bibliothèque au moment de la compilation. Vous ne pouvez donc compter que sur la stabilité de leurs interfaces et non sur leurs implémentations.
Mais en raison du manque d'ABI standard, vous ( ne pouvez généralement pas passer ces objets en toute sécurité au-delà des limites du module. Un GCC shared_ptr
est probablement différent d’un MSVC shared_ptr
, qui peut aussi différer d’un Intel shared_ptr
. Même avec le même compilateur , la compatibilité binaire de ces classes n'est pas garantie.
En bout de ligne, si vous souhaitez distribuer une version pré-construite de votre bibliothèque, vous avez besoin d'une ABI standard sur laquelle vous baser. C n’en a pas, mais les fournisseurs de compilateurs sont très doués pour l’interopérabilité entre les bibliothèques C pour une plate-forme donnée - il existe des normes de facto.
La situation n’est pas aussi bonne pour C++. Les compilateurs individuels peuvent gérer l'interopérabilité entre leurs propres fichiers binaires. Vous avez donc la possibilité de distribuer une version pour chaque compilateur pris en charge, souvent GCC et MSVC. Mais à la lumière de cela, la plupart des bibliothèques n’exportent que l’interface C, ce qui signifie des pointeurs bruts.
Cependant, le code non-bibliothèque devrait généralement préférer les pointeurs intelligents au brut.
Il peut y avoir plusieurs raisons. Pour en énumérer quelques-uns:
Edit: L'utilisation de pointeurs intelligents est le choix du développeur. Cela dépend de divers facteurs.
Dans les systèmes critiques de performances, vous pouvez ne pas vouloir utiliser les pointeurs intelligents, ce qui génère des frais généraux.
Le projet qui a besoin de la compatibilité ascendante, vous pouvez ne pas vouloir utiliser les pointeurs intelligents qui ont des fonctionnalités spécifiques à C++ 11
Edit2 Il y a une chaîne de plusieurs votes négatifs en l'espace de 24 heures en raison du passage en dessous. Je n'arrive pas à comprendre pourquoi la réponse est refusée même si, ci-dessous, il ne s'agit que d'une suggestion complémentaire et non d'une réponse.
Cependant, C++ vous permet toujours d’avoir les options ouvertes. :) par exemple.
template<typename T>
struct Pointer {
#ifdef <Cpp11>
typedef std::unique_ptr<T> type;
#else
typedef T* type;
#endif
};
Et dans votre code, utilisez-le comme:
Pointer<int>::type p;
Pour ceux qui disent qu'un pointeur intelligent et un pointeur brut sont différents, je suis d'accord avec cela. Le code ci-dessus était juste une idée dans laquelle on peut écrire un code qui est interchangeable avec juste un #define
, Ce n'est pas compulsion ;
Par exemple, T*
Doit être supprimé explicitement, mais pas le pointeur intelligent. Nous pouvons avoir une Destroy()
basée sur un modèle pour gérer cela.
template<typename T>
void Destroy (T* p)
{
delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
// do nothing
}
et l'utiliser comme:
Destroy(p);
De la même manière, un pointeur brut peut être copié directement et un pointeur intelligent, une opération spéciale.
Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));
Où Assign()
est comme:
template<typename T>
T* Assign (T *p)
{
return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
// use move sematics or whateve appropriate
}
Il existe deux problèmes avec les pointeurs intelligents (antérieurs à C++ 11):
Le pointeur intelligent par défaut, en ce sens qu'il est gratuit, est unique_ptr
. Malheureusement, il nécessite une sémantique de déplacement C++ 11, qui n'est apparue que récemment. Tous les autres pointeurs intelligents ont un coût (shared_ptr
, intrusive_ptr
) ou ont une sémantique moins qu'idéale (auto_ptr
).
Avec C++ 11 au coin de la rue, apportant un std::unique_ptr
, on serait tenté de penser que c'est enfin fini ... Je ne suis pas si optimiste.
Seuls quelques compilateurs majeurs implémentent la plupart de C++ 11, et seulement dans leurs versions récentes. Nous pouvons nous attendre à ce que de grandes bibliothèques telles que QT et Boost soient disposées à conserver la compatibilité avec C++ 03 pendant un certain temps, ce qui empêche quelque peu l'adoption à grande échelle des nouveaux et brillants pointeurs intelligents.
Ne vous éloignez pas des pointeurs intelligents, ils sont particulièrement utiles dans les applications où vous devez faire passer un objet.
Les bibliothèques ont tendance à simplement renvoyer une valeur ou à renseigner un objet. Ils n’ont généralement pas d’objets devant être utilisés dans de nombreux endroits; ils n’ont donc pas besoin d’utiliser des pointeurs intelligents (du moins pas dans leur interface, ils peuvent les utiliser en interne).
Je pourrais prendre comme exemple une bibliothèque sur laquelle nous travaillons, où après quelques mois de développement, je me suis rendu compte que nous n’utilisions que des pointeurs et des pointeurs intelligents dans quelques classes (3-5% de toutes les classes).
Passer variables par référence était suffisant dans la plupart des endroits, nous utilisions des pointeurs intelligents chaque fois que nous avions un objet pouvant être null, et des pointeurs bruts quand une bibliothèque que nous utilisions nous y obligeait.
Edit (Je ne peux pas commenter à cause de ma réputation): passer des variables par référence est très flexible: si vous voulez que l'objet soit en lecture seule, vous pouvez utiliser une référence const (vous pouvez toujours faire des conversions désagréables être capable d’écrire l’objet) mais vous bénéficiez du maximum de protection possible (c’est la même chose avec les pointeurs intelligents). Mais je suis d'accord qu'il est beaucoup plus agréable de simplement renvoyer l'objet.
Qt a inutilement réinventé de nombreuses parties de la bibliothèque Standard dans le but de devenir Java. Je pense qu’il dispose actuellement de ses propres indicateurs intelligents, mais en général, ce n’est pas un summum en matière de design. À ma connaissance, wxWidgets a été conçu bien avant que des indicateurs intelligents utilisables ne soient écrits.
Quant à Boost, je m'attends à ce qu'ils utilisent des pointeurs intelligents chaque fois que nécessaire. Vous devrez peut-être être plus précis.
De plus, n'oubliez pas qu'il existe des pointeurs intelligents pour imposer la propriété. Si l'API n'a pas de sémantique de propriété, alors pourquoi utiliser un pointeur intelligent?
Bonne question. Je ne connais pas les articles spécifiques auxquels vous faites référence, mais j'ai lu des choses semblables de temps en temps. Je soupçonne que les rédacteurs de tels articles ont tendance à avoir un préjugé contre la programmation de type C++. Si le rédacteur ne programme en C++ que quand il le doit, il retourne alors à Java ou dès que possible, alors il ne partage pas vraiment la mentalité C++.
On soupçonne que certains ou la plupart des mêmes écrivains préfèrent les gestionnaires de mémoire qui ramassent les ordures. Je ne le fais pas, mais je pense différemment qu'eux.
Les pointeurs intelligents sont excellents, mais ils doivent garder le compte des références. La tenue de comptes de référence entraîne des coûts - coûts souvent modestes, mais coûts néanmoins - au moment de l'exécution. Il n'y a rien de mal à économiser ces coûts en utilisant des pointeurs nus, en particulier si les pointeurs sont gérés par des destructeurs.
L'un des points forts du C++ est son support pour la programmation de systèmes intégrés. L'utilisation de pointeurs nus en fait partie.
Mise à jour: Un intervenant a correctement observé que le nouveau unique_ptr
(disponible depuis TR1) ne compte pas les références. Le commentateur a également une définition du "pointeur intelligent" différente de celle que j'ai en tête. Il a peut-être raison à propos de la définition.
Mise à jour ultérieure: Le fil de commentaire ci-dessous est éclairant. Il est recommandé de lire tout cela.
Il existe également d'autres types de pointeurs intelligents. Vous voudrez peut-être un pointeur intelligent spécialisé pour quelque chose comme la réplication réseau (celui qui détecte s’il ya accès et envoie des modifications au serveur, etc.), conserve un historique des modifications, marque le fait qu’il a été consulté afin qu’il puisse être examiné lorsque vous enregistrez des données sur le disque et ainsi de suite. Vous ne savez pas si cela est la meilleure solution pour le pointeur, mais l'utilisation des types de pointeurs intelligents intégrés dans les bibliothèques peut entraîner le blocage des utilisateurs et la perte de flexibilité.
Les utilisateurs peuvent avoir toutes sortes d'exigences et de solutions en matière de gestion de la mémoire, en plus des pointeurs intelligents. Je souhaiterais peut-être gérer moi-même la mémoire, car je pourrais allouer de l'espace pour les éléments d'un pool de mémoire afin de les allouer à l'avance et non au moment de l'exécution (utile pour les jeux). J'utilise peut-être une implémentation de C++ ramassée par les ordures (C++ 11 rend cela possible bien que rien n'existe encore). Ou peut-être que je ne fais tout simplement pas assez avancé pour me soucier de les déranger, je peux savoir que je ne vais pas oublier les objets non initialisés, etc. Peut-être que je suis juste confiant dans ma capacité à gérer la mémoire sans la béquille du pointeur.
L'intégration avec C est un autre problème également.
Un autre problème est que les pointeurs intelligents font partie de la STL. C++ est conçu pour être utilisable sans la STL.
Cela dépend également du domaine dans lequel vous travaillez. J'écris des moteurs de jeu pour gagner leur vie, nous évitons les boosters comme la peste, dans les jeux, les frais généraux liés aux boosters ne sont pas acceptables. Dans notre moteur principal, nous avons fini par écrire notre propre version de stl (un peu comme le ea stl).
Si je devais écrire une application de formulaires, je pourrais envisager d'utiliser des pointeurs intelligents; mais une fois que la gestion de la mémoire est une seconde nature, ne pas avoir de contrôle granulaire sur la mémoire devient assez ennuyant.