Quand dois-je utiliser des pointeurs bruts sur des pointeurs intelligents?

Après avoir lu cette réponse , il semble que l’utilisation des pointeurs intelligents soit la meilleure possible et que l’utilisation de pointeurs “normaux” / bruts soit réduite au minimum.

Est-ce vrai?

Non ce n’est pas vrai. Si une fonction a besoin d’un pointeur et n’a rien à voir avec la propriété, alors je crois fermement qu’un pointeur régulier devrait être transmis pour les raisons suivantes:

  • Pas de propriété, donc vous ne savez pas quel type de pointeur intelligent à passer
  • Si vous passez un pointeur spécifique, comme shared_ptr , vous ne pourrez pas passer, par exemple, scoped_ptr

La règle serait la suivante – si vous savez qu’une entité doit posséder un certain type de propriété de l’object, utilisez toujours des pointeurs intelligents – celui qui vous donne le type de propriété dont vous avez besoin. S’il n’y a pas de notion de propriété, n’utilisez jamais de pointeurs intelligents.

Exemple 1:

 void PrintObject(shared_ptr po) //bad { if(po) po->Print(); else log_error(); } void PrintObject(const Object* po) //good { if(po) po->Print(); else log_error(); } 

Exemple2:

 Object* createObject() //bad { return new Object; } some_smart_ptr createObject() //good { return some_smart_ptr(new Object); } 

Utiliser des pointeurs intelligents pour gérer la propriété est la bonne chose à faire. Inversement, l’utilisation de pointeurs bruts là où la propriété n’est pas un problème n’est pas une erreur.

Voici quelques utilisations parfaitement légitimes des pointeurs bruts (rappelez-vous, ils sont toujours supposés ne pas être propriétaires):

où ils rivalisent avec des références

  • argument en passant; mais les références ne peuvent être nulles, donc sont préférables
  • en tant que membres de classe pour désigner l’association plutôt que la composition; généralement préférable aux références car la sémantique de l’assignation est plus simple et en outre, un invariant mis en place par les constructeurs peut garantir qu’ils ne sont pas 0 pour la durée de vie de l’object
  • comme un handle à un object (éventuellement polymorphe) possédé ailleurs; les références ne peuvent être nulles encore une fois, ils sont préférables
  • std::bind utilise une convention où les arguments passés sont copiés dans le foncteur résultant; cependant std::bind(&T::some_member, this, ...) ne fait qu’une copie du pointeur alors que std::bind(&T::some_member, *this, ...) copie l’object; std::bind(&T::some_member, std::ref(*this), ...) est une alternative

où ils ne sont pas en concurrence avec des références

  • comme iterators!
  • argument passant des parameters facultatifs ; ici ils rivalisent avec boost::optional
  • comme un handle à un object (éventuellement polymorphe) possédé ailleurs, quand ils ne peuvent pas être déclarés sur le site d’initialisation; encore une fois, en concurrence avec boost::optional

Pour rappel, il est presque toujours faux d’écrire une fonction (qui n’est pas un constructeur ou un membre de la fonction qui prend possession, par exemple) qui accepte un pointeur intelligent à moins de le transmettre à un constructeur (par exemple, std::async car sémantiquement, il est proche d’appeler le constructeur std::thread ). Si c’est synchrone, pas besoin du pointeur intelligent.


Pour rappel, voici un extrait illustrant plusieurs des utilisations ci-dessus. Nous écrivons et utilisons une classe qui applique un foncteur à chaque élément d’un std::vector en écrivant une sortie.

 class apply_and_log { public: // C++03 exception: it's acceptable to pass by pointer to const // to avoid apply_and_log(std::cout, std::vector()) // notice that our pointer would be left dangling after call to constructor // this still adds a requirement on the caller that v != 0 or that we throw on 0 apply_and_log(std::ostream& os, std::vector const* v) : log(&os) , data(v) {} // C++0x alternative // also usable for C++03 with requirement on v apply_and_log(std::ostream& os, std::vector const& v) : log(&os) , data(&v) {} // now apply_and_log(std::cout, std::vector {}) is invalid in C++0x // && is also acceptable instead of const&& apply_and_log(std::ostream& os, std::vector const&&) = delete; // Notice that without effort copy (also move), assignment and destruction // are correct. // Class invariants: member pointers are never 0. // Requirements on construction: the passed stream and vector must outlive *this typedef std::function const&)> callback_type; // optional callback // alternative: boost::optional void do_work(callback_type* callback) { // for convenience auto& v = *data; // using raw pointers as iterators int* begin = &v[0]; int* end = begin + v.size(); // ... if(callback) { callback(v); } } private: // association: we use a pointer // notice that the type is polymorphic and non-copyable, // so composition is not a reasonable option std::ostream* log; // association: we use a pointer to const // consortingved example for the constructors std::vector const* data; }; 

L’utilisation de pointeurs intelligents est toujours recommandée, car ils documentent clairement la propriété.

Ce qui nous manque vraiment, cependant, c’est un pointeur intelligent «vierge», qui n’implique aucune notion de propriété.

 template  class ptr // thanks to Martinho for the name suggestion :) { public: ptr(T* p): _p(p) {} template  ptr(U* p): _p(p) {} template  ptr(SP const& sp): _p(sp.get()) {} T& operator*() const { assert(_p); return *_p; } T* operator->() const { assert(_p); return _p; } private: T* _p; }; // class ptr 

Il s’agit, en effet, de la version la plus simple de tout pointeur intelligent pouvant exister: un type qui indique qu’il ne possède pas la ressource pointée.

Une instance où le comptage de référence (utilisé par shared_ptr en particulier) est interrompu est lorsque vous créez un cycle à partir des pointeurs (par exemple, A pointe vers B, B pointe vers A ou A-> B-> C-> A ou etc). Dans ce cas, aucun des objects ne sera jamais automatiquement libéré, car ils conservent tous le nombre de références de chacun supérieur à zéro.

Pour cette raison, chaque fois que je crée des objects ayant une relation parent-enfant (par exemple, une arborescence d’objects), j’utiliserai shared_ptrs dans les objects parents pour contenir leurs objects enfants, mais si les objects enfants ont besoin d’un pointeur sur leur parent , J’utiliserai un simple pointeur C / C ++ pour cela.

Peu de cas où vous pouvez utiliser des pointeurs:

  • Pointeurs de fonction (évidemment pas de pointeur intelligent)
  • Définir votre propre pointeur ou conteneur intelligent
  • Traiter avec la programmation de bas niveau, où les pointeurs bruts sont cruciaux
  • Décroissant des masortingces brutes

Je pense qu’une réponse un peu plus approfondie a été donnée ici: Quel type de pointeur est-ce que j’utilise quand?

Extrait de ce lien: “Utilisez des pointeurs muets (pointeurs bruts) ou des références pour des références non propriétaires aux ressources et lorsque vous savez que la ressource va survivre à l’object / à la scope référençant.” (gras conservé de l’original)

Le problème est que si vous écrivez du code pour un usage général, il n’est pas toujours facile d’être absolument certain que l’object survivra au pointeur brut. Considérez cet exemple:

 struct employee_t { employee_t(const std::ssortingng& first_name, const std::ssortingng& last_name) : m_first_name(first_name), m_last_name(last_name) {} std::ssortingng m_first_name; std::ssortingng m_last_name; }; void replace_current_employees_with(const employee_t* p_new_employee, std::list& employee_list) { employee_list.clear(); employee_list.push_back(*p_new_employee); } void main(int argc, char* argv[]) { std::list current_employee_list; current_employee_list.push_back(employee_t("John", "Smith")); current_employee_list.push_back(employee_t("Julie", "Jones")); employee_t* p_person_who_convinces_boss_to_rehire_him = &(current_employee_list.front()); replace_current_employees_with(p_person_who_convinces_boss_to_rehire_him, current_employee_list); } 

À sa grande surprise, la fonction replace_current_employees_with() peut, par inadvertance, provoquer la désallocation d’un de ses parameters avant de l’utiliser.

Donc, même si, à première vue, il semble que la fonction replace_current_employees_with() ne nécessite pas la propriété de ses parameters, elle nécessite une sorte de défense contre la possibilité que ses parameters soient libérés insidieusement avant de les utiliser. La solution la plus simple consiste à prendre en charge (temporairement partagé) la propriété du ou des parameters, probablement via un shared_ptr .

Mais si vous ne voulez vraiment pas vous approprier, il y a maintenant une option sûre – et c’est la partie sans prétention de la réponse – ” pointeurs enregistrés “. Les “pointeurs enregistrés” sont des pointeurs intelligents qui se comportent comme des pointeurs bruts, sauf qu’ils sont automatiquement définis sur null_ptr lorsque l’object cible est détruit. Par défaut, ils lancent une exception si vous essayez d’accéder à un object déjà supprimé. .

Notez également que les pointeurs enregistrés peuvent être “désactivés” (remplacés automatiquement par leur homologue de pointeur brut) par une directive de compilation, leur permettant d’être utilisés (et d’engendrer des frais généraux) uniquement dans les modes debug / test / beta. Donc, vous devriez vraiment devoir recourir à des pointeurs réels assez rarement.

C’est vrai. Je ne peux pas voir les avantages des pointeurs bruts sur les pointeurs intelligents, en particulier dans un projet complexe.

Pour une utilisation tempory et légère, les pointeurs bruts sont bons.