Pourquoi les bibliothèques et les frameworks C ++ n’utilisent-ils jamais de pointeurs intelligents?

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, que ce soit des pointeurs étendus ou partagés.

Cependant, j’ai remarqué que les frameworks tels que Qt, wxWidgets et les bibliothèques comme Boost ne reviennent jamais et n’attendent jamais de pointeurs intelligents, comme s’ils ne les utilisaient pas du tout. Au lieu de cela, ils renvoient ou attendent des pointeurs bruts. Y a-t-il une raison à cela? Devrais-je restr à l’écart des pointeurs intelligents lorsque j’écris une API publique, et pourquoi?

Je me demande simplement pourquoi les pointeurs intelligents sont recommandés lorsque de nombreux projets majeurs 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 binary d’application C ++ standard.

Si vous écrivez une bibliothèque contenant uniquement des en-têtes, vous pouvez transmettre des pointeurs intelligents et des conteneurs standard au contenu de votre choix. Leur source est disponible pour votre bibliothèque au moment de la compilation, vous comptez donc uniquement sur la stabilité de leurs interfaces et non sur leurs implémentations.

Mais en raison de l’absence d’ABI standard, vous ne pouvez généralement pas transmettre ces objects en toute sécurité à travers les limites des modules. Un GCC shared_ptr est probablement différent d’un MSVC shared_ptr , qui peut également différer d’un Intel shared_ptr . Même avec le même compilateur, ces classes ne sont pas nécessairement compatibles entre les versions.

L’essentiel est que si vous souhaitez dissortingbuer une version prédéfinie de votre bibliothèque, vous avez besoin d’un ABI standard sur lequel vous pouvez compter. C n’en a pas, mais les fournisseurs de compilateurs sont très compétents en matière d’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 binarys, vous avez donc la possibilité de dissortingbuer une version pour chaque compilateur pris en charge, souvent GCC et MSVC. Mais à la lumière de cela, la plupart des bibliothèques exportent simplement une interface C, ce qui signifie des pointeurs bruts.

Le code hors bibliothèque devrait toutefois préférer les pointeurs intelligents aux bruts.

Il peut y avoir plusieurs raisons. Pour en énumérer quelques-uns:

  1. Les pointeurs intelligents sont devenus partie intégrante de la norme récemment. Jusqu’alors ils faisaient partie d’autres bibliothèques
  2. Leur principale utilisation est d’éviter les memory leaks; de nombreuses bibliothèques ne disposent pas de leur propre gestion de la mémoire; Généralement, ils fournissent des utilitaires et des API
  3. Ils sont implémentés en tant que wrapper, car ce sont en fait des objects et non des pointeurs. Ce qui a un coût en temps / espace supplémentaire par rapport aux pointeurs bruts; Les utilisateurs des bibliothèques peuvent ne pas vouloir avoir de tels frais généraux

Edit : L’utilisation de pointeurs intelligents est un choix de développeur complet. Cela dépend de divers facteurs.

  1. Dans les systèmes critiques, vous ne souhaiterez peut-être pas utiliser des pointeurs intelligents qui génèrent des frais généraux.

  2. Le projet qui nécessite la compatibilité descendante, vous ne souhaiterez peut-être pas utiliser les pointeurs intelligents dotés de fonctionnalités spécifiques à C ++ 11

Edit2 Il y a plusieurs enchaînements en l’espace de 24 heures en raison du passage inférieur. Je n’arrive pas à comprendre pourquoi la réponse est abaissée, même si ci-dessous n’est qu’une suggestion supplémentaire et non une réponse.
Cependant, C ++ vous facilite toujours l’ouverture des options. 🙂 par exemple

 template struct Pointer { #ifdef  typedef std::unique_ptr type; #else typedef T* type; #endif }; 

Et dans votre code, utilisez-le comme:

 Pointer::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 où l’on peut écrire un code interchangeable uniquement avec un #define , ce n’est pas une contrainte ;

Par exemple, T* doit être supprimé explicitement, mais pas un pointeur intelligent. Nous pouvons avoir un modèle Destroy() pour gérer cela.

 template void Destroy (T* p) { delete p; } template void Destroy (std::unique_ptr p) { // do nothing } 

et l’utiliser comme:

 Destroy(p); 

De la même manière, pour un pointeur brut, nous pouvons le copier directement et pour un pointeur intelligent, nous pouvons utiliser des opérations spéciales.

 Pointer::type p = new X; Pointer::type p2(Assign(p)); 

Assign() est comme:

 template T* Assign (T *p) { return p; } template ... Assign (SmartPointer &p) { // use move sematics or whateve appropriate } 

Il y a deux problèmes avec les pointeurs intelligents (pre C ++ 11):

  • non standards, donc chaque bibliothèque a tendance à réinventer ses propres problèmes (syndr & dépendances NIH)
  • coût potentiel

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 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 fini … Je ne suis pas si optimiste.

Seuls quelques grands compilateurs implémentent la majeure partie de C ++ 11, et seulement dans leurs versions récentes. Nous pouvons nous attendre à ce que les 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 pointeurs intelligents.

Vous ne devriez pas restr à l’écart des pointeurs intelligents, ils ont leur utilisation en particulier dans les applications où vous devez faire circuler un object.

Les bibliothèques ont tendance à simplement renvoyer une valeur ou à remplir un object. Ils n’ont généralement pas d’objects à utiliser dans de nombreux endroits, il n’est donc pas nécessaire qu’ils utilisent 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 avons travaillé, après quelques mois de développement, j’ai réalisé que nous utilisions uniquement des pointeurs et des pointeurs intelligents dans quelques classes (3-5% de toutes les classes).

Passer des variables par référence était suffisant dans la plupart des cas, nous avons utilisé des pointeurs intelligents chaque fois que nous avions un object qui pouvait être nul, et des pointeurs bruts quand une bibliothèque que nous utilisions nous obligeait à le faire.

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’object soit en lecture seule, vous pouvez utiliser une référence const (vous pouvez toujours faire des castings désagréables pour pouvoir écrire l’object ) mais vous obtenez le maximum de protection possible (c’est la même chose avec les pointeurs intelligents). Mais je suis d’accord sur le fait qu’il est beaucoup plus agréable de renvoyer l’object.

Qt a inutilement réinventé de nombreuses parties de la bibliothèque standard pour tenter de devenir Java. Je crois qu’il a effectivement ses propres pointeurs intelligents maintenant, mais en général, ce n’est pas un sumt du design. À ma connaissance, wxWidgets a été conçu bien avant que des pointeurs intelligents utilisables soient écrits.

Quant à Boost, je m’attends à ce qu’ils utilisent des pointeurs intelligents, le cas échéant. Vous devrez peut-être être plus précis.

De plus, n’oubliez pas que des pointeurs intelligents existent 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 similaires de temps en temps. Je pense que les auteurs de ces articles ont tendance à avoir un parti pris contre la programmation de style C ++. Si l’écrivain programme en C ++ uniquement lorsqu’il doit le faire, il retourne ensuite à Java ou plus tard, alors il ne partage pas vraiment l’état d’esprit C ++.

On soupçonne que certains ou la plupart des auteurs préfèrent les gestionnaires de mémoire collecteurs de déchets. Je ne sais pas, mais je pense différemment de ce qu’ils font.

Les pointeurs intelligents sont excellents, mais ils doivent conserver des chiffres de référence. La tenue des comptes de référence entraîne des coûts – souvent des coûts modestes, mais néanmoins des coûts – à l’exécution. Il n’y a rien de mal à économiser ces coûts en utilisant des pointeurs, surtout si les pointeurs sont gérés par des destructeurs.

L’un des excellents avantages de C ++ est sa prise en charge de la programmation de systèmes intégrés. L’utilisation de pointeurs nus en fait partie.

Mise à jour: Un commentateur a correctement observé que le nouveau unique_ptr C ++ (disponible depuis TR1) ne compte pas les références. Le commentateur a également une définition différente du «pointeur intelligent» que je n’ai en tête. Il a peut-être raison sur la définition.

Mise à jour supplémentaire: Le fil de commentaire ci-dessous est éclairant. Tout est recommandé lecture.

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 de réseau (qui détecte l’access et envoie des modifications au serveur ou à certaines de celles-ci), conserve un historique des modifications, indique qu’il a été consulté vous enregistrez les données sur le disque et ainsi de suite. Vous ne savez pas si faire cela dans le pointeur est la meilleure solution, mais l’utilisation de types de pointeurs intelligents intégrés dans les bibliothèques peut entraîner le locking des utilisateurs et la perte de flexibilité.

Les personnes peuvent avoir toutes sortes d’exigences de gestion de la mémoire et de solutions différentes au-delà des pointeurs intelligents. Je pourrais vouloir gérer moi-même la mémoire, je pourrais allouer de l’espace pour des choses dans un pool de mémoire afin qu’il soit alloué à l’avance et non à l’exécution (utile pour les jeux). J’utilise peut-être une implémentation de C ++ récupérée (C ++ 11 rend cela possible bien qu’aucun n’existe encore). Ou peut-être que je ne fais rien de suffisamment avancé pour m’inquiéter de les déranger, je peux savoir que je ne vais pas oublier les objects non initialisés, etc. Peut-être que je suis juste confiant dans ma capacité à gérer la mémoire sans la béquille de pointeur.

L’intégration avec C est un autre problème.

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 aussi du domaine dans lequel vous travaillez. J’écris des moteurs de jeu pour gagner ma vie, nous évitons le coup de pouce comme le fléau, dans les jeux la surcharge de boost n’est pas acceptable. Dans notre moteur de base, nous avons fini par écrire notre propre version de stl (un peu comme la première étape).

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 un contrôle granulaire sur la mémoire devient ennuyeux.