2009-02-18 6 views
3

Le code avec lequel je travaille a sa propre implémentation de pointeur intelligent qui effectue un simple comptage de références. Oui, nous ne devrions pas avoir notre propre implémentation. Oui, nous devrions utiliser un boost ou un autre. Ours avec moi.Problèmes lors de l'écriture d'un constructeur de copie pour un pointeur intelligent

Je trouve que je voulais écrire du code comme ceci:

... 
CountedPointer<Base> base; 
... 
CountedPointer<Derived> derived; 
... 
base = derived; 

Cependant, le constructeur de copie pour CountedPointer a un prototype comme celui-ci:

CountedPointer(const CountedPointer<T> &other); 

Ainsi, le code ci-dessus ne sera pas compilé depuis il ne peut pas trouver un constructeur approprié (ou un opérateur d'affectation - c'est la même histoire là). J'ai essayé de réécrire le constructeur de copie avec un prototype comme celui-ci:

template<U> 
CountedPointer(const CountedPointer<U> &other); 

Cependant, je frappe le problème que le constructeur de copie doit accéder à un membre privé de l'objet qu'elle copie (par exemple le pointeur brut) et si elle est dans une spécialisation différente de CountedPointer, ils ne sont pas visibles. Alexandrascu évite ce problème dans sa bibliothèque Loki en ayant des fonctions d'accesseur pour le pointeur encapsulé, mais je préférerais ne pas donner un accès direct au pointeur brut si possible.

Y at-il un moyen que je peux écrire cela pour permettre la dérivée à la copie de base, mais ne permet pas l'accès général au pointeur brut?

Mise à jour: J'ai implémenté la réponse acceptée ci-dessous, et cela fonctionne bien. J'ai passé un moment à comprendre pourquoi mon programme se trompait terriblement lorsque je ne fournissais que la version du constructeur de copie, en remplacement de la version originale non-modélisée. Finalement, je me suis rendu compte que le compilateur ne considère pas la version modélisée comme étant un constructeur de copie, et en fournit un par défaut. Celui par défaut copie simplement le contenu sans mettre à jour le compteur, donc je me retrouve avec des pointeurs pendants et des doubles libres. Le même genre de chose s'applique à l'opérateur d'affectation.

+0

Il est assez difficile d'empêcher l'accès au pointeur brut. & * CounterPointer () est généralement un T * brut. Ne fonctionne pas bien avec les pointeurs null cependant. – MSalters

+0

Je me sens comme fournir une fonction dans l'interface suggère que l'utilisation du pointeur brut est une bonne idée. Si quelqu'un décide qu'il doit aller au pointeur brut, il modifiera l'interface de toute façon. C'est plutôt mineur. Si c'était mon code, ça ne me dérangerait pas trop. – Ned

Répondre

4

Vous ne pouvez pas les rendre amis? Comme:

template<typename T> 
class CountedPointer { 

    // ... 

    template<U> 
    CountedPointer(const CountedPointer<U> &other); 

    template<typename U> friend class CountedPointer; 
}; 
+0

Ça fait l'affaire! Je me demande si vous pourriez ajouter quelques détails sur ce qui se passe ici? Par exemple, je ne suis pas exactement sûr pourquoi la déclaration d'ami est modélisée. – Ned

+0

Je ne suis pas sûr comment le U dans la déclaration de modèle d'ami est lié à l'U dans la déclaration de modèle de constructeur de copie –

+0

@Doug T. le U dans l'ami est un peu sans rapport avec le U dans la copie ctor. J'aurais pu utiliser V pour l'ami, et ça marcherait de la même façon. – mitchnull

3

« Alexandrescu évite ce problème dans sa bibliothèque Loki en ayant des fonctions accesseurs pour le pointeur encapsulé, mais je préfère ne pas donner un accès direct au pointeur brut si possible »

Je pense que le coût de l'ajout d'un getter pointeur brut va être beaucoup moins que le coût de la complexité d'essayer de contourner n'ayant pas accès brut. Il n'existe tout simplement pas de mécanisme de langage pour convertir les instances entre deux classes de modèles non apparentées. Pour le compilateur, ils sont deux choses complètement différentes sans relation au moment de l'exécution. C'est la raison pour laquelle une instance de classe template ne peut pas accéder aux autres private.

Vous pouvez envisager de créer une telle relation avec une classe de base pour tous les CountedPointers. Vous devrez peut-être mettre un vide * dans cette classe de base. Ensuite, vous devrez faire toutes les vérifications vous-même (est-ce que T est dérivé de U alors forcez la distribution ... Puis-je implicitement convertir un T en U?, Si donc forcer une conversion .. etc) mais cela peut devenir assez complexe . Voici un début approximatif pour cette approche:

class CountedPointerBase 
{ 
    void* rawPtr; 
}; 

template <class T> 
class CountedPointer : public ConutedPointerBase 
{ 
     T* myRawT = reinterpret_cast<T*>(rawPtr); 

     template<class U> 
     CountedPointer(CountedPointer<U> u) 
     { 
      // Copying a CountedPointer<T> -> CountedPointer<U> 
      if (dynamic_cast<U*>(myRawT) != NULL) 
      { 
       // Safe to copy one rawPtr to another 
      } 
      // check for all other conversions 
     } 
} 

Il peut y avoir beaucoup d'autres complexités à voir si deux types sont compatibles. Peut-être y a-t-il un lissage de modèle Loki/Boost qui peut déterminer deux arguments de type si vous pouviez les convertir l'un à l'autre.

Quoi qu'il en soit, comme vous pouvez le voir, cela peut être une solution beaucoup plus complexe que l'ajout d'un getter.Etre capable d'obtenir le pointeur brut a également d'autres avantages. Vous pouvez passer des pointeurs bruts dans des fonctions de bibliothèque qui n'acceptent que des pointeurs bruts et les utiliser comme temporaires, par exemple. Cela peut être dangereux, je suppose, si quelqu'un dans votre équipe décide de conserver une copie du pointeur brut par opposition au pointeur intelligent. Cette personne devrait probablement être sommairement giflée à la truite.

0

Vous résoudre le problème si vous avez un CountedPointer (T * autre); constructeur et un opérateur d'affectation similaire.

+0

Cela ne signifie-t-il pas qu'un CountedPointer est implicitement convertible en T *? C'est le coeur de son problème, ils ne sont vraiment pas .... –

+0

Yeap, c'est impliqué. Je ne vois aucun problème à déclarer un opérateur T * que retournera le pointeur encapsulé. – sharptooth

+0

Je préfère ne donner à personne l'accès au pointeur brut. Et s'ils le suppriment? Ou le garder au-delà de la durée de vie du CounterPointer? Permettre la conversion implicite permet aussi de "supprimer cp;" compiler pour un cp CountedPointer - et il laissera un pointeur dangling dans cp. – Ned

Questions connexes