2010-10-06 4 views
2

Voici mon problème. Je crée ma propre interface graphique. Tous les Widgets sont dans un conteneur qui a des fonctions d'ajout et de suppression. Les widgets dérivent d'une classe de widget de base. Voici où je suis incertain. Je voudrais idéalement un flux comme celui-ci: l'utilisateur crée un pointeur (widget souhaité dérivant de la classe de base), le conteneur alloue et gère les ressources, l'utilisateur a un pointeur vers le widget et peut y effectuer des appels.Aide avec ceci (pointeurs et polymorphisme)

Cependant, le polymorphisme rend cette confusion. Comment pourrais-je obtenir mon conteneur pour créer le bon type de nouveau? Le problème ici est que n'importe qui peut créer un nouveau widget (comme SuperTextBoxWidget), c'est pourquoi fournir une chaîne et faire un changement ne résoudrait pas cela.

Mon autre solution de correction rapide est de rendre l'utilisateur responsable de faire le nouveau, et de fournir le pointeur vers la fonction d'ajout du conteneur. Mais cela ne me semble pas idiot, et il semble étrange que l'utilisateur fasse l'allocation initiale, mais alors le conteneur gère le reste, y compris l'effacement.

Quel serait le meilleur et le plus propre à cet égard?

Merci

juste une idée de ce que j'ai jusqu'à présent:

class AguiWidgetContainer 
{ 

    std::vector<AguiWidgetBase*> widgets; 
    public: 
     AguiWidgetContainer(void); 
     ~AguiWidgetContainer(void); 
     void handleEvent(ALLEGRO_EVENT* event); 
     int add(AguiWidgetBase *widget); 
     bool remove(int widgetId); 
}; 
+0

Une classe Factory avec une interface pour autoriser les extensions utilisateur serait appropriée. De cette façon, l'utilisateur fournit les en-têtes et le code d'instanciation, mais dans un cadre contrôlé. La responsabilité de créer les objets resterait alors étroitement contrôlée. –

Répondre

0

Mon autre alternative quick-fix est de rendre l'utilisateur responsable de faire nouveau, et en fournissant le pointeur sur la fonction d'ajout du conteneur.Mais ce ne me semble pas idiot, et il semble étrange que l'utilisateur fasse l'allocation initiale , mais le conteneur gère le reste, y compris l'effacement .

Pourquoi cela vous semble-t-il étrange?

Si un conteneur commence à prendre la responsabilité de l'allocation des objets à contenir, il enfreint SRP. Je vous recommande de concevoir vos interfaces pour accepter les pointeurs vers les objets Widget alloués par les utilisateurs. Le conteneur est uniquement responsable de les contenir/stocker/les récupérer.

L'utilisateur prend également la responsabilité de supprimer les objets alloués.

La plupart des conteneurs STL fonctionnent de cette manière.

+0

Ok, je vais le faire de cette façon – jmasterx

+0

Les conteneurs n'allouant pas les objets est une conséquence de l'utilisation de pointeurs pour activer le polymorphisme. En général, les conteneurs construisent par défaut, copient-construisent et suppriment ensuite les éléments. Mais ils ne détruisent jamais ceux qu'ils ont créés, donc aucune règle n'est violée (à condition que l'ODR soit respecté, car les templates peuvent être confrontés à de gros problèmes avec les appels entre DLL construits avec différents compilateurs.) –

+0

@Ben Voigt: 'Comment Je reçois mon conteneur pour créer le bon type de nouveau? 'Je voulais dire que le conteneur ne devrait pas fournir d'interfaces telles que 'créer' dans lequel il devient responsable de la création de l'élément à stocker. Si la construction de copie, etc., fait partie de la sémantique du conteneur, alors ce n'est pas un problème. Je le considère comme un prérequis pour être 'containable' et pas une fonctionnalité de base du conteneur – Chubsdad

1

je suggère l'emprunt de COM et de rendre votre classe widget de base d'une interface virtuelle pure qui comprend une fonction pour se détruire. Ensuite, les implémenteurs de votre widget n'ont même pas besoin d'utiliser le même allocateur (important si vous avez déjà croisé des limites DLL).

EDIT: Exemple:

class IWidget 
{ 
public: 
    virtual Size Measure() = 0; 
    virtual void Draw(Point) = 0; 
    //and so on 

    virtual void Release() = 0; 
}; 

class TextBoxWidget : public IWidget 
{ 
    TextBoxWidget() {} 
    ~TextBoxWidget() {} 
public: 
    // implement IWidget functions, etc, etc 

    static TextBoxWidget* Create() { return new TextBoxWidget(); } 
    virtual void Release() { delete this; } 
}; 

Maintenant TextBoxWidget ne peut être créé avec TextBoxWidget::Create() et libéré avec someTBW->Release(), et utilise toujours new et delete dans la même DLL, garantissant qu'ils correspondent.

+0

Je ne suis pas sûr de bien comprendre le concept. Pourriez-vous élaborer? Merci – jmasterx

+0

Cela ne signifie-t-il pas toujours que l'utilisateur appelle create()? – jmasterx

+0

Absolument. Mais la classe du widget contrôle à la fois l'allocation et la désallocation, il n'y a pas de possibilité de discordance. C'est ce qui vous préoccupait, n'est-ce pas, que l'utilisateur allouerait et que le conteneur se déchargerait d'une fonction incompatible? Ce style d'objet se désaffecte définitivement, il est utilisé par des centaines de milliers de programmes. –

1

Je peux penser à au moins deux façons de le faire.

1. Fournir une version du modèle de add:

template<class T> 
int add() { 
    widgets.push_back(new T); 
} 

2. Utilisez une classe d'usine:

Vous pouvez avoir une base factory class qui définit les méthodes d'allouer (et peut-être aussi libre) widgets. Vos utilisateurs fournissent alors leur propre sous-classe de l'usine qui crée le type correct de widget. Par exemple:

class AguiWidgetFactory { 
    AguiWidgetBase *createWidget() = 0; 
}; 

class AguiSuperWidgetFactory : public AguiWidgetFactory { 
    AguiWidgetBase *createWidget() { 
    return new SuperTextBoxWidget(); 
    } 
}; 

Votre méthode add prend alors un objet usine en entrée et utilise pour créer un nouveau widget.

+0

Comment fonctionne la première méthode? Je ne vois pas comment les templates résolvent le problème de création – jmasterx

+0

Vos utilisateurs spécifient simplement le nom de classe dans le modèle (il n'y a pas d'autres paramètres) et le compilateur génère automatiquement du code pour créer l'objet correct, par exemple. vous pouvez appeler 'ajouter ()'. – casablanca

+0

Comment puis-je appeler des méthodes de la classe de base? ex: widgets [i] .clicked(); dois-je faire (WidgetBase) widgets [i] .clicked(); – jmasterx