2009-11-08 5 views
0

Je dois créer une porte-objet générique classe. Je suis venu avec quelque chose de simple commeClasse de porte-objet générique - C++

template<typename T> 
class ObjectCarrier 
{ 

public: 
    const T& item() const 
    { 
     return item_; 
    } 

    void setItem(T& item) 
    { 
     item_ = item; 
    } 

private: 
    T item_; 
}; 

Cela fonctionne bien quand T a obtenu un constructeur par défaut (sans paramètre). Les choses se compliquent quand T a des constructeurs paramétrés. Donc, je réécris la classe comme

template<typename T> 
class ObjectCarrier 
{ 

public: 
    const T& item() const 
    { 
     return *item_; 
    } 

    void setItem(T& item) 
    { 
     item_ = new T (item); 
    } 

private: 
    T* item_; 
}; 

changé la variable item_ à T* et créé une nouvelle instance en utilisant le constructeur de copie de T. Encore une fois cela a bien fonctionné jusqu'à ce que T soit un type de pointeur. Je veux dire ObjectCarrier<Foo*> ne fonctionnera pas.

Je me demande comment puis-je concevoir cette classe afin qu'elle fonctionne pour presque tous les types de types. Je pense que je devrais créer un type traits spécialisé pour les pointeurs. Mais malheureusement, je ne suis pas capable de faire ce travail.

Toute aide serait géniale.

+1

Pourquoi le premier fonctionne pas avec les constructeurs avec des paramètres? Vous n'avez sûrement besoin que d'un constructeur de copie? Ou ai-je oublié quelque chose? – Goz

+0

@Goz: Il vous manque quelque chose. Lorsque l'objet est créé, la valeur par défaut (vide ou avec tous les paramètres par défaut) est utilisée pour construire le membre item_. – rmn

Répondre

0

Vous pouvez utiliser la spécialisation de modèle pour le type T* et réécrire les méthodes aux pointeurs de suite. Vous pouvez faire quelque chose comme:

template<typename T> 
class ObjectCarrier<T*> 
{ 
    public: 
    const T* item() const 
    { 
     return item_; 
    } 

    void setItem(T* item) 
    { 
     item_ = item; 
    } 

private: 
    T* item_; 

}; 
+0

Vous voulez spécialiser 'ObjectCarrier' pour les pointeurs? –

+0

Ceci est OK pour les petites classes comme 'ObjectCarrier'. Mais est-ce la seule option? Je pense que cela sera difficile lors de la création de collections personnalisées qui auront plusieurs méthodes. Comment STL l'implémente? Ont-ils des spécialisations de 'vector' pour tous les types? Merci d'avoir répondu. Le vecteur –

+2

nécessite que les objets soient configurables par défaut afin que ce problème ne se pose pas. Il y a une spécialisation de vecteur pour le vecteur pour implémenter une sorte de champs de bits, bien que cette implémentation soit dangereuse à utiliser. – Naveen

0

Il existe un modèle de conception qui est peut-être pertinent pour ce - Memento. Un peu hors sujet, mais gardez à l'esprit que dès que vous commencez à ajouter des objets dans votre classe, vous aurez besoin d'un moyen de gérer la mémoire. Je suggère d'utiliser un std :: auto_ptr au moins. Vous devrez également fournir un constructeur de copie et un opérateur d'affectation, lors de l'utilisation de std :: auto_ptr.

0

Il pourrait être possible de tenir l'objet en valeur et encore différer sa construction avec l'utilisation d'un placement nouveau et quelque chose comme ce qui suit:

#include <iostream> 
#include <cassert> 

template <class T> 
class ObjectCarrier 
{ 
public: 
    ObjectCarrier(): ref(0) {} 
    ObjectCarrier(const ObjectCarrier& other): ref(0) 
    { 
     set_data(other.ref); 
    } 
    ~ObjectCarrier() 
    { 
     clear(); 
    } 
    const ObjectCarrier& operator = (const ObjectCarrier& other) 
    { 
     if (other.empty()) 
      clear(); 
     else 
      set_data(other.ref); 
     return *this; 
    } 
    void set(const T& value) 
    { 
     set_value(value); 
    } 
    const T& get() const 
    { 
     assert(!empty() && "No object being carried"); 
     return *ref; 
    } 
    bool empty() const 
    { 
     return ref == 0; 
    } 
    void clear() 
    { 
     if (!empty()) { 
      ref->~T(); 
      ref = 0; 
     } 
    } 
private: 
    char data[sizeof(T)]; 
    T* ref; 
    void set_value(const T& value) 
    { 
     if (!empty()) { 
      *ref = value; 
     } 
     else { 
      ref = new (data) T(value); 
     } 
    } 
    void set_data(const T* value) 
    { 
     if (value) { 
      set_value(*value); 
     } 
    } 
}; 

int main() 
{ 
    ObjectCarrier<int> i; 
    ObjectCarrier<int> j(i); 
    i = j; 
    i.set(10); 
    std::cout << i.get() << '\n'; 
    j = i; 
    i.set(20); 
    std::cout << i.get() << ' ' << j.get() << ' ' << ObjectCarrier<int>(i).get() << '\n'; 
} 

Cependant, je peu en question l'utilité de cette classe. Peut-être le seul but qu'il pourrait avoir, serait d'agir comme Boost.Optional.

Mais si vous ne voulez pas la classe pour être en mesure de ne pas tenir une valeur, juste donner un constructeur paramétrisé:

template<typename T> 
class ObjectCarrier 
{ 

public: 
    ObjectCarrier(const T& value = T()): 
     item_(value) 
    { 
    } 
    const T& item() const 
    { 
     return item_; 
    } 

    void setItem(T& item) 
    { 
     item_ = item; 
    } 

private: 
    T item_; 
}; 

(Il est juste que cette classe semble plutôt inutile, à moins peut-être comme façade pour le code qui s'attend à ce que les variables aient des méthodes item et setItem, plutôt que, disons, un opérateur d'affectation.)

+0

Votre première idée pourrait avoir besoin d'un indice sur les problèmes d'alignement. – sbi

+0

Désolé, quels problèmes d'alignement? – UncleBens

+0

Peut-être qu'il me manque quelque chose, mais quand vous écrivez 'char data [sizeof (T)]', comment savez-vous que le tableau sera aligné pour un objet de type 'T'? – sbi

2

Les approches ci-dessus sont beaucoup trop compliquées. Restez simple, et résolvez simplement le problème des arguments construct en utilisant des constructeurs de templates. N'utilisez pas de pointeurs, ils créeront la vie de l'objet et la copie des maux de tête.

Voici une implémentation que j'utilise beaucoup. Les constructeurs de modèles transmettront les arguments pour les choses directement sur l'objet imbriqué ce qui est pratique.Les valeurs de l'opérateur T & permettent de passer de carrier<T> à des fonctions qui prennent un type T, sans copie onéreuse. Vous pouvez envelopper des objets qui prennent jusqu'à deux arguments avec ce code.

/* A wrapper of type T */ 
template <typename T> 
struct carrier { 

    carrier() {} 
    template <typename A1> carrier(const A1& a1) : value(a1) {} 
    template <typename A1, typename A2> carrier(const A1& a1, const A2& a2) : value(a1, a2) {} 

    operator T&() { return value; } 
    operator const T&() const { return value; } 

    T value; 
}; 

Vous pouvez l'utiliser comme ceci:

const carrier<point> p1(10,10); // make p1 const to stop people changing it 
showPoint(p1);     // calls a function that expects a point, 
showPoint(p1.value);    // access the point directly 
Questions connexes