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

156

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 enveloppés dans des pointeurs intelligents, qu'il s'agisse de pointeurs étendus ou partagés.

Cependant, j'ai remarqué que les frameworks comme 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 retournent ou s'attendent à des pointeurs bruts. Y a-t-il une raison à cela? Dois-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 projets majeurs semblent les éviter.

Laurent
la source
22
Toutes ces bibliothèques que vous venez de nommer ont été créées il y a de nombreuses années. Les pointeurs intelligents ne sont devenus vraiment standard qu'en C ++ 11.
chrisaycock
22
les pointeurs intelligents ont une surcharge (comptage de références, etc.) - qui peut être critique - dans les systèmes embarqués / temps réel par exemple. IMHO - les pointeurs intelligents sont pour les programmeurs paresseux. De nombreuses API optent également pour le plus petit dénominateur commun. Je sens les flammes se lécher autour de mes pieds pendant que je tape!
Ed Heal
93
@EdHeal: La raison pour laquelle vous pouvez sentir les flammes se lécher autour de vos pieds est que vous vous trompez complètement à tous égards. Par exemple, quels frais généraux y a-t-il unique_ptr? Pas du tout. Les Qt / WxWidgets sont-ils ciblés sur des systèmes embarqués ou en temps réel? Non, ils sont destinés à Windows / Mac / Unix sur un bureau au maximum. Les pointeurs intelligents sont destinés aux programmeurs qui souhaitent le corriger.
Chiot
24
Vraiment, les téléphones mobiles utilisent Java.
R. Martinho Fernandes
12
Des pointeurs intelligents uniquement vraiment standard en C ++ 11? Quoi??? Ces choses sont utilisées depuis plus de 20 ans.
Kaz

Réponses:

124

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 binaire d'application (ABI) C ++ standard.

Si vous écrivez une bibliothèque d'en-tête uniquement, vous pouvez transmettre des pointeurs intelligents et des conteneurs standard à votre guise. 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 passer ces objets en toute sécurité à travers les limites du module. Un GCC shared_ptrest 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 garanties d'être compatibles binaires entre les versions.

L'essentiel est que si vous souhaitez distribuer une version prédéfinie de votre bibliothèque, vous avez besoin d'une ABI standard sur laquelle vous appuyer. C n'en a pas, mais les fournisseurs de compilateurs sont très bons 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 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 exportent simplement une interface C - et cela signifie des pointeurs bruts.

Cependant, le code non-bibliothèque devrait généralement préférer les pointeurs intelligents au brut.

Jon Purdy
la source
17
Je suis d'accord avec vous, même passer une chaîne std :: string peut être pénible, cela en dit long sur C ++ en tant que "grand langage pour les bibliothèques".
Ha11owed le
8
La ligne du bas est plutôt la suivante: si vous voulez distribuer une version pré-build, vous devez le faire pour chaque compilateur que vous souhaitez prendre en charge.
josefx
6
@josefx: Oui c'est triste mais vrai, la seule alternative est COM ou une interface C brute. Je souhaite que le comité C ++ commence à s'inquiéter pour ce genre de problèmes. Je veux dire, ce n'est pas comme si C ++ était un nouveau langage d'il y a 2 ans.
Robot Mess
3
J'ai voté contre parce que c'est faux. Les problèmes ABI sont plus que gérables dans la plupart des cas. Bien que peu convivial, ABI est également difficilement insurmontable.
Puppy
4
@NathanAdams: Un tel logiciel est sans aucun doute impressionnant et utile. Mais il traite le symptôme de problèmes plus profonds: la sémantique C ++ de la durée de vie et de la propriété se situe quelque part entre appauvri et inexistant. Ces tas de bogues ne se seraient pas produits si le langage ne les permettait pas. Donc, bien sûr, les pointeurs intelligents ne sont pas une panacée - ils tentent de récupérer certaines des pertes encourues en utilisant C ++ en premier lieu.
Jon Purdy
40

Il peut y avoir plusieurs raisons. Pour en citer quelques-uns:

  1. Les pointeurs intelligents sont devenus une partie de la norme tout récemment. Jusque-là, ils faisaient partie d'autres bibliothèques
  2. Leur utilisation principale est d'éviter les fuites de mémoire; de nombreuses bibliothèques n'ont pas leur propre gestion de la mémoire; Généralement, ils fournissent des utilitaires et des API
  3. Ils sont implémentés comme wrapper, car ce sont en fait des objets et non des pointeurs. Ce qui a un coût supplémentaire en temps / espace, 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 entièrement le choix du développeur. Cela dépend de divers facteurs.

  1. Dans les systèmes à performances critiques, vous ne souhaiterez peut-être pas utiliser de pointeurs intelligents qui génèrent une surcharge

  2. Le projet qui a besoin de la compatibilité descendante, vous ne voudrez peut-être pas utiliser les pointeurs intelligents qui ont des fonctionnalités spécifiques à C ++ 11

Edit2 Il y a une série de plusieurs votes négatifs en l'espace de 24 heures en raison du passage ci-dessous. Je ne comprends pas pourquoi la réponse est rejetée même si ci-dessous n'est qu'une suggestion complémentaire et non une réponse.
Cependant, C ++ vous facilite toujours l'ouverture des options. :) 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 où l'on peut écrire un code qui est interchangeable juste 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<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

et utilisez-le 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 une opération spéciale.

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

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
}
iammilind
la source
14
Sur 3. Certains pointeurs intelligents ont des coûts de temps / d'espace supplémentaires, d'autres non, y compris std::auto_ptrcela fait partie de la norme depuis longtemps (et notez que j'aime std::auto_ptrcomme type de retour pour les fonctions créant des objets, même si c'est presque inutile partout ailleurs). En C ++ 11 std::unique_ptrn'a pas de coûts supplémentaires par rapport à un simple pointeur.
David Rodríguez - dribeas
4
Exactement ... il y a une belle symétrie sur l'apparition unique_ptret la disparition de auto_ptr, le code ciblant C ++ 03 devrait utiliser le plus tard, tandis que le code ciblant C ++ 11 peut utiliser l'ancien. Les pointeurs intelligents ne le sont passhared_ptr , il existe de nombreuses normes et aucune norme, y compris des propositions à la norme qui ont été rejetées commemanaged_ptr
David Rodríguez - dribeas
2
@iammilind, ce sont des points intéressants, mais le plus drôle est que si nous finissons par utiliser des pointeurs intelligents, comme apparemment beaucoup le recommandent, nous finissons par créer du code incompatible avec les principales bibliothèques. Bien sûr, nous pouvons envelopper / dérouler les pointeurs intelligents au besoin, mais cela semble être beaucoup de tracas et créerait un code incohérent (parfois nous traitons avec des pointeurs intelligents, parfois non).
laurent
7
L'affirmation selon laquelle les pointeurs intelligents ont un «coût supplémentaire en temps / espace» est quelque peu trompeuse; tous les pointeurs intelligents, à l'exception du unique_ptrcoût d'exécution, unique_ptrsont de loin celui qui est le plus couramment utilisé. L'exemple de code que vous fournissez est également trompeur, car unique_ptret ce T*sont des concepts entièrement différents. Le fait que vous vous référiez aux deux comme typedonne l'impression qu'ils peuvent être échangés l'un contre l'autre.
void-pointer le
12
Vous ne pouvez pas taper comme ça, ces types ne sont en aucun cas équivalents. Ecrire des typedefs comme celui-ci pose des problèmes.
Alex B
35

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

  • non-standards, donc chaque bibliothèque a tendance à réinventer la sienne (problèmes de syndrom et de dépendances NIH)
  • coût potentiel

Le pointeur intelligent par défaut , en ce 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 grands compilateurs implémentent la plupart de C ++ 11, et uniquement 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 et brillants pointeurs intelligents.

Matthieu M.
la source
12

Vous ne devriez pas rester à l'écart des pointeurs intelligents, ils ont leur utilité en particulier dans les applications où vous devez passer un objet.

Les bibliothèques ont tendance à renvoyer simplement une valeur ou à remplir un objet. Ils n'ont généralement pas d'objets qui doivent être utilisés 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é, où, après quelques mois de développement, j'ai réalisé que nous n'utilisions que 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 endroits, nous utilisions des pointeurs intelligents chaque fois que nous avions un objet qui pouvait être nul, et des pointeurs bruts lorsqu'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 quand même faire de vilains casts pour pouvoir écrire l'objet ) mais vous obtenez le 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.

Robot Mess
la source
Je ne suis pas en désaccord, avec vous, exactement, mais je ferai remarquer qu'il existe une école de pensée qui déprécie le passage de références variables dans la plupart des cas. J'avoue que j'adhère à cette école. Je préfère que les fonctions ne modifient pas leurs arguments. En tout cas, pour autant que je sache, les références de variables de C ++ ne font rien pour empêcher une mauvaise gestion des objets auxquels elles se réfèrent, ce que les pointeurs intelligents ont l'intention de faire.
thb
2
vous avez const pour cela (il semble que je puisse commenter: D).
Robot Mess
9

Qt a inutilement réinventé de nombreuses parties de la bibliothèque Standard pour tenter de devenir Java. Je pense qu'il a en fait ses propres pointeurs intelligents maintenant, mais en général, ce n'est pas le summum du design. wxWidgets, pour autant que je sache, a été conçu bien avant l'écriture des pointeurs intelligents utilisables.

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 renforcer la propriété. Si l'API n'a pas de sémantique de propriété, alors pourquoi utiliser un pointeur intelligent?

Chiot
la source
19
Qt a été écrit avant qu'une grande partie de la fonctionnalité ne soit suffisamment répandue sur les plates-formes qu'il souhaitait utiliser. Il a des pointeurs intelligents depuis longtemps et les utilise pour faire le partage implicite des ressources dans presque toutes les classes Q *.
rubenvb
6
Chaque bibliothèque GUI réinvente inutilement la roue. Même les chaînes, Qt a QString, wxWidgets a wxString, MFC a horriblement nommé CString. Un UTF-8 n'est-il pas std::stringassez bon pour 99% des tâches de l'interface graphique?
Inverse le
10
@Inverse QString a été créé lorsque std :: string n'était pas là.
MrFox
Vérifiez quand qt a été créé et quels pointeurs intelligents étaient disponibles à ce moment-là.
Dainius
3

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 soupçonne que les auteurs de tels articles ont tendance à entretenir un parti pris contre la programmation de style C ++. Si l'écrivain programme en C ++ seulement quand il le doit, puis retourne à Java ou tel dès qu'il le peut, alors il ne partage pas vraiment l'état d'esprit C ++.

On soupçonne que certains ou la plupart des mêmes écrivains préfèrent les gestionnaires de mémoire de récupération de place. Je ne le fais pas, mais je pense différemment d'eux.

Les pointeurs intelligents sont excellents, mais ils doivent garder le nombre de références. La tenue des comptages de référence entraîne des coûts - souvent modestes, mais néanmoins des coûts - au moment de l'exécution. Il n'y a rien de mal à économiser ces coûts en utilisant des pointeurs nus, surtout si les pointeurs sont gérés par des destructeurs.

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

Mise à jour: Un commentateur a correctement observé que le nouveau C ++ unique_ptr(disponible depuis TR1) ne compte pas les références. Le commentateur a également une définition de «pointeur intelligent» différente de celle que j'ai à l'esprit. Il a peut-être raison sur la définition.

Mise à jour supplémentaire: le fil de commentaires ci-dessous est éclairant. Tout cela est une lecture recommandée.

thb
la source
2
Pour commencer, la programmation des systèmes embarqués est une vaste minorité de toute la programmation, et tout à fait hors de propos. C ++ est un langage à usage général. Deuxièmement, shared_ptrconserve un décompte de références. Il existe de nombreux autres types de pointeurs intelligents qui ne conservent pas du tout un nombre de références. Enfin, les bibliothèques mentionnées sont destinées aux plates-formes qui ont beaucoup de ressources à revendre. Ce n’est pas que j’ai été le contrevenant, mais tout ce que je dis, c’est que votre message est plein de faux.
Chiot
2
@thb - Je suis d'accord avec vous. DeadMG - Essayez de vivre sans systèmes embarqués. Oui - certains pointeurs intelligents n'ont pas de frais généraux, mais certains en ont. L'OP mentionne les bibliothèques. Boost, par exemple, a des composants qui sont utilisés par les systèmes embarqués - mais les pointeurs intelligents peuvent être inappropriés pour certaines applications.
Ed Heal
2
@EdHeal: Ne pas vivre sans systèmes embarqués! = La programmation pour eux n'est pas une minuscule minorité sans importance. Les pointeurs intelligents conviennent à chaque situation dans laquelle vous devez gérer la durée de vie d'une ressource.
Puppy
4
shared_ptrn'a pas de frais généraux. Il n'a de surcharge que si vous n'avez pas besoin de sémantique de propriété partagée thread-safe, ce qu'il fournit.
R. Martinho Fernandes
1
Non, shared_ptr a une surcharge significative par rapport au minimum nécessaire pour la sémantique de la propriété partagée thread-safe; spécifiquement, il alloue un bloc de tas distinct de l'objet réel que vous partagez, dans le seul but de stocker le refcount. intrusive_ptr est plus efficace, mais (comme shared_ptr) il suppose également que chaque pointeur vers l'objet sera un intrusive_ptr. Vous pouvez obtenir une surcharge encore plus faible que intrusive_ptr avec un pointeur partagé de comptage de ref personnalisé, comme je le fais dans mon application, puis utiliser T * chaque fois que vous pouvez garantir qu'au moins un pointeur intelligent survivra à la valeur T *.
Qwertie
2

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 (un qui détecte s'il y a accès et envoie des modifications au serveur ou quelque chose du genre), conserve un historique des modifications, marque le fait qu'il a été accédé afin qu'il puisse être étudié lorsque vous enregistrez des données sur le disque et ainsi de suite. Je ne sais pas si faire cela dans le pointeur est la meilleure solution, mais l'utilisation des types de pointeurs intelligents intégrés dans les bibliothèques pourrait entraîner le verrouillage des personnes et perdre la flexibilité.

Les gens peuvent avoir toutes sortes d'exigences et de solutions de gestion de la mémoire différentes au-delà des pointeurs intelligents. Je pourrais vouloir gérer la mémoire moi-même, je pourrais allouer de l'espace pour des choses dans un pool de mémoire afin qu'il soit alloué à l'avance et non au moment de l'exécution (utile pour les jeux). J'utilise peut-être une implémentation garbage collector de C ++ (C ++ 11 rend cela possible bien qu'il n'en existe pas encore). Ou peut-être que je ne fais rien d'assez avancé pour me soucier de m'embêter avec eux, je peux savoir que je n'oublierai pas les objets non initialisés et ainsi de suite. 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 également 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.

David C. Bishop
la source
" Un autre problème est que les pointeurs intelligents font partie du TSL. " Ils ne le sont pas.
curiousguy
0

Cela dépend aussi du domaine dans lequel vous travaillez. J'écris des moteurs de jeu pour gagner ma vie, nous évitons les boosters comme la peste, dans les jeux, la surcharge de boost n'est pas acceptable. Dans notre moteur principal, nous avons fini par écrire notre propre version de stl (un peu comme ea stl).

Si je devais rédiger 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 ennuyeux.

Ugly Davis
la source
3
Il n'y a pas de "surcharge de boost".
curiousguy
4
Je n'ai jamais vu shared_ptr ralentir mon moteur de jeu à un degré notable. Cependant, ils ont accéléré le processus de production et de débogage. Aussi, qu'entendez-vous exactement par «la surcharge du boost»? C'est une assez grande couverture à jeter.
derpface
@curiousguy: C'est l'overhead de compilation de tous ces en-têtes et macro + template vaudou ...
einpoklum