2010-11-06 3 views
1

Je veux un conteneur de pointeur C++ sécurisé similaire à scoped_ptr de boost, mais avec une sémantique de copie de type valeur. J'ai l'intention de l'utiliser pour un élément très rarement utilisé de classe très fortement utilisée dans la boucle la plus interne d'une application pour obtenir une meilleure localisation mémoire. En d'autres termes, je ne me soucie pas de la performance de cette classe tant que sa charge de mémoire "en ligne" est faible.Ce conteneur de pointeur C++ est-il sécurisé?

J'ai commencé avec ce qui suit, mais je ne suis pas très doué pour ça; est le coffre-fort suivant? Est-ce que je réinvente la roue et si oui, où devrais-je regarder?

template <typename T> 
class copy_ptr { 
    T* item; 
public: 
    explicit copy_ptr() : item(0) {} 
    explicit copy_ptr(T const& existingItem) : item(new T(existingItem)) {} 
    copy_ptr(copy_ptr<T> const & other) : item(new T(*other.item)) {} 
    ~copy_ptr() { delete item;item=0;} 

    T * get() const {return item;} 
    T & operator*() const {return *item;} 
    T * operator->() const {return item;} 
}; 

Edit: oui, il est intentionnel que ce comportement à peu près exactement comme une valeur normale. Le profilage montre que l'algorithme est par ailleurs assez efficace mais est parfois gêné par des échecs de cache. En tant que tel, j'essaie de réduire la taille de l'objet en extrayant de gros blobs qui sont actuellement inclus par valeur mais ne sont pas réellement utilisés dans les boucles les plus internes. Je préférerais faire cela sans changements sémantiques - un simple wrapper de template serait idéal.

+2

Hmm ... Je ne suis pas sûr de savoir à quoi cela servirait. L'objet réel lui-même présente déjà une sémantique de copie, alors pourquoi utiliser un pointeur? C'est comme si vous réimplémentiez la sémantique d'une variable de pile automatique ici. –

+1

* C'est ce que je veux! * Comme mentionné dans la question, cela améliore la localisation de la mémoire. Il est utilisé dans une simulation avec des paramètres assez lourds, écrits pour la clarté, mais où les paramètres ne sont pas réellement pertinents lors de l'exécution du calcul. Je ne peux pas mettre les paramètres à la fin de l'objet car l'héritage est en cours et les paramètres sont déclarés dans une classe ancêtre. –

+1

Vous n'utilisez pas un emplacement nouveau ou quoi que ce soit pour contrôler où la copie est créée, alors pourquoi avoir une copie de l'objet dans un emplacement aléatoire dans le tas améliore la localité plutôt que d'avoir une copie de l'objet sur les autres variables locales la pile? Quelle architecture est-ce? –

Répondre

5

Non, ce n'est pas le cas.

Vous avez oublié l'opérateur d'affectation.

Vous pouvez choisir de interdire l'affectation (étrange lors de la copie est autorisée) en déclarant l'opérateur privé d'affectation (et non sa mise en œuvre), ou vous pouvez le mettre en œuvre ainsi:

copy_ptr& operator=(copy_ptr const& rhs) 
{ 
    using std::swap; 

    copy_ptr tmp(rhs); 
    swap(this->item, tmp.item); 
    return *this; 
} 

Vous avez également oublié dans le constructeur de copie qui other.item peut être nul (en conséquence du constructeur par défaut), choisissez votre solution:

// 1. Remove the default constructor 

// 2. Implement the default constructor as 
copy_ptr(): item(new T()) {} 

// 3. Implement the copy constructor as 
copy_ptr(copy_ptr const& rhs): item(other.item ? new T(*other.item) : 0) {} 

Pour le comportement de la valeur comme je préférerais 2, car une valeur ne peut pas être nu ll. Si vous optez pour la nullité, introduit assert(item); à la fois operator-> et operator* pour assurer l'exactitude (en mode débogage) ou lancer une exception (ce que vous préférez).

Enfin le item = 0 dans le destructeur est inutile: vous ne pouvez pas utiliser l'objet une fois qu'il a été détruit de toute façon sans invoquer un comportement indéfini ...

Il y a aussi la remarque de Roger Pate à propos de la propagation const-ness être plus « valeur-like » mais il est plus une question de sémantique que la décision correcte.

+1

'copy_ptr & operator = (copy_ptr rhs) {swap (* ceci, rhs); retour * ceci; } ami échange de void (copy_ptr & a, copy_ptr & b) {std :: échange (a.item, b.item); } ' –

+0

Pourquoi ne pas simplement définir l'opérateur d'affectation comme' copy_ptr & operator = (copy_ptr const & rhs) {* item = * rhs.item; retour * ceci; } ' –

+0

Oh et merci - c'est en effet le bug! Cependant, ce wrapper ne résout pas mes problèmes, bien que ce soit encore un peu, * petit * peu compréhensible peu de pratique :-). –

3

Vous devriez "passer" la const-ness du type copy_ptr:

T* get() { return item; } 
T& operator*() { return *item; } 
T* operator->() { return item; } 

T const* get() const { return item; } 
T const& operator*() const { return *item; } 
T const* operator->() const { return item; } 

Spécification T n'est pas nécessaire dans la copie cteur:

copy_ptr(copy_ptr const &other) : item (new T(*other)) {} 

Pourquoi avez-vous fait défaut ctor explicite? Nulling le pointeur dans le dtor n'a de sens que si vous prévoyez sur UB quelque part ...

Mais ce sont tous des problèmes mineurs, ce que vous avez il y a à peu près tout. Et oui, j'ai vu cela inventé plusieurs fois, mais les gens ont tendance à modifier légèrement la sémantique à chaque fois. Vous pouvez regarder boost :: optionnel, car c'est presque ce que vous avez écrit ici lorsque vous le présentez, sauf si vous ajoutez une sémantique de déplacement et d'autres opérations.

+0

Lors de l'utilisation du modèle ci-dessus, mon application se bloque avec corruption de tas dans ce destructeur sur l'arrêt de l'application dans le thread finaliseur GC .NET, donc dans le processus de trouver le bogue j'ai ajouté le harmless = 0; juste pour tester si je ne suis pas en train de déconner quelque chose et par exemple. copier le pointeur et double-supprimer en quelque sorte - mais en vain, et par conséquent cette question de savoir si la classe est sain d'esprit en premier lieu :-). –

+0

@Eamon: Vous ne pouvez jamais voir que cet élément a été annulé si vous accédez à un copy_ptr après qu'il a été détruit, et ce serait UB. Vous pouvez trouver la corruption de tas dans ce Dtor, mais c'est facilement parce que la corruption se produit ailleurs et vous ne pouvez pas le détecter jusqu'à ce que vous utilisiez le tas (ce que supprime). - Bien qu'il semble que la réponse de Mattieu explique pourquoi. –

+0

+1 pour const correction –

1

En plus de ce que Roger a dit, vous pouvez utiliser Google 'clone_ptr' pour des idées/comparaisons.

+0

Merci, je peux certainement faire quelque chose avec ça! –

+0

Si vous allez sur la route de clonage, rendez-le compatible avec le concept Boost 'Clonability' soit en déclarant une base virtuelle clone() const = 0;' dans votre objet 'Base' ou en fournissant' Base * new_clone (Base const *) ; 'fonction libre. Cela rendra votre classe utilisable avec la bibliothèque Boost Pointer Container. –