84 votes

Quand dois-je utiliser des pointeurs C ++ sur des pointeurs intelligents?

Après avoir lu cette réponse , il semble que c'est une meilleure pratique d'utiliser autant que possible des pointeurs intelligents et de réduire au minimum l'utilisation de pointeurs "normaux".

Est-ce vrai?

117voto

Armen Tsirunyan Points 59548

Non, il 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 normal devrait être adoptée pour les raisons suivantes:

  • Aucun droit de propriété, donc vous ne savez pas quel type de pointeur intelligent de passer
  • Si vous passez un pointeur spécifiques, comme shared_ptr, alors vous ne serez pas en mesure de passer, disons, scoped_ptr

La règle serait ce - si vous savez qu'une entité doit prendre un certain type de propriété de l'objet, toujours utiliser des pointeurs intelligents - celui qui vous donne le type de propriété que vous avez besoin. Si il n'y a pas de notion de propriété, ne jamais utiliser des pointeurs intelligents.

Exemple1:

void PrintObject(shared_ptr<const Object> 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<Object> createObject() //good
{
   return some_smart_ptr<Object>(new Object);
}

19voto

Luc Danton Points 21421

À l'aide de pointeurs intelligents pour gérer la propriété est le droit chose à faire. À l'inverse, à l'aide de matières pointeurs partout où la propriété n'est pas une question qui est pas mal.

Voici quelques parfaitement légitime utilisation brute des pointeurs (rappelez-vous, il est toujours supposé qu'ils sont non-propriétaire):

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

  • argument en passant; mais les références ne peuvent pas être null, sont préférables
  • en tant que membres de la classe pour désigner l'association plutôt que la composition; généralement préférable de références parce que la sémantique de la cession sont plus simple et en plus un invariant mis en place par les constructeurs peuvent s'assurer qu'ils ne sont pas 0 pendant la durée de vie de l'objet
  • comme une poignée pour une (peut-être polymorphe) objet appartenant à un autre endroit références ne peut pas être null donc ils sont préférables
  • std::bind utilise une convention où les arguments qui sont passés sont copiés dans la foncteur; toutefois std::bind(&T::some_member, this, ...) seulement fait une copie du pointeur de la souris alors qu' std::bind(&T::some_member, *this, ...) des copies de l'objet; std::bind(&T::some_member, std::ref(*this), ...) est une alternative

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

  • comme les itérateurs!
  • l'argument de passage de l' option paramètres; ici, ils sont en concurrence avec boost::optional<T&>
  • comme une poignée pour une (peut-être polymorphe) objet appartenant à un autre endroit, quand ils ne peuvent pas être déclarés sur le site de l'initialisation, de nouveau, en concurrence avec d' boost::optional<T&>

Pour rappel, c'est presque toujours tort d'écrire une fonction (qui n'est pas un constructeur, ou une fonction de membre, par exemple, prend possession) qui accepte un pointeur intelligent, sauf si elle à son tour le transmettre à un constructeur (par exemple, c'est correct pour std::async parce que du point de vue sémantique, c'est près d'être un appel à l' std::thread constructeur). Si c'est synchrone, pas besoin de pointeur intelligent.


Pour rappel, voici un extrait qui illustre plusieurs des utilisations ci-dessus. Nous sommes à l'écrit et à l'aide d'une classe qui applique un foncteur à chaque élément d'un std::vector<int> lors de l'écriture sur la 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<int>())
    // 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<int> 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<int> const& v)
        : log(&os)
        , data(&v)
    {}
    // now apply_and_log(std::cout, std::vector<int> {}) is invalid in C++0x
    // && is also acceptable instead of const&&
    apply_and_log(std::ostream& os, std::vector<int> 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<void(std::vector<int> const&)> callback_type;

    // optional callback
    // alternative: boost::optional<callback_type&>
    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
    // contrived example for the constructors
    std::vector<int> const* data;
};

4voto

Matthieu M. Points 101624

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

Ce qui nous manque vraiment, cependant, est un pointeur intelligent "vierge", qui n'implique aucune notion de propriété.

 template <typename T>
class ptr // thanks to Martinho for the name suggestion :)
{
public:
  ptr(T* p): _p(p) {}
  template <typename U> ptr(U* p): _p(p) {}
  template <typename SP> 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<T>
 

C'est, en effet, la version la plus simple de tout pointeur intelligent qui puisse exister: un type qui documente qu'il ne possède pas la ressource qu'il pointe également.

4voto

Jeremy Friesner Points 16684

Dans un cas, le comptage de référence (utilisé par les shared_ptr en particulier) se décomposent est lorsque vous créez un cycle sur les pointeurs (par exemple, Un des points à B, de B les points pour Un, ou A->B->C->A, ou etc). Dans ce cas, aucun de ces objets ne sera jamais libérée automatiquement, parce qu'ils sont tous en gardant des uns et des autres comptes de référence supérieure à zéro.

Pour cette raison, chaque fois que je suis en créant des objets qui ont une relation parent-enfant (par exemple, un arbre d'objets), je vais utiliser shared_ptrs dans les objets parents à tenir leur enfant objets, mais si l'enfant objets ont besoin d'un pointeur de retour à leur parent, je vais utiliser un simple C/C++ pointeur pour que.

4voto

iammilind Points 29275

Quelques cas, où vous voudrez peut-être utiliser des pointeurs:

  • Pointeurs de fonction (évidemment pas de pointeur intelligent)
  • Définition de votre propre pointeur intelligent ou conteneur
  • Gérer la programmation de bas niveau, où les pointeurs bruts sont cruciaux
  • Décomposition des tableaux bruts

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X