Question

Il semble que j'ai eu un malentendu fondamental à propos de C ++: <

J'aime la solution de conteneur polymorphe. Merci beaucoup, pour avoir porté cela à mon attention:)

Nous avons donc besoin de créer un objet de type conteneur relativement générique. Il arrive également à encapsuler une logique liée aux affaires. Cependant, nous devons stocker des données essentiellement arbitraires dans ce conteneur, des types de données primitifs aux classes complexes.

Ainsi, on passerait immédiatement à l’idée d’une classe de modèles et on en finirait avec elle. Cependant, j'ai remarqué que le polymorphisme C ++ et les modèles ne fonctionnent pas bien ensemble. Etant donné qu’il existe une logique complexe sur laquelle nous allons devoir travailler, je préférerais simplement rester soit avec les modèles, soit avec le polymorphisme, et ne pas essayer de combattre le C ++ en le faisant faire les deux.

Enfin, étant donné que je veux faire l’un ou l’autre, je préférerais le polymorphisme. Je trouve qu'il est beaucoup plus facile de représenter des contraintes telles que & "Ce conteneur contient des types comparables &"; - à la java.

Cela m'amène au sujet de la question: au plus abstrait, j'imagine que je pourrais avoir un & "conteneur &"; interface virtuelle pure qui a quelque chose qui ressemble à & "; push (void * data) et pop (void * data) " (pour mémoire, je n’essaye pas réellement d’implémenter une pile).

Cependant, je n'aime pas trop les valeurs void * au niveau supérieur, sans parler de la modification de la signature à chaque fois que je souhaite ajouter une contrainte au type de données avec lequel un conteneur concret peut fonctionner.

Résumé: nous avons des conteneurs relativement complexes qui permettent de récupérer des éléments de différentes manières. Nous voulons pouvoir varier les contraintes sur les éléments pouvant aller dans les conteneurs. Les éléments doivent fonctionner avec plusieurs types de conteneurs (dans la mesure où ils respectent les contraintes de ce conteneur particulier).

Edit: Je devrais également mentionner que les conteneurs eux-mêmes doivent être polymorphes. C’est la raison principale pour laquelle je ne souhaite pas utiliser le C ++ basé sur un modèle.

Alors, dois-je laisser tomber mon amour pour les interfaces de type Java et utiliser des modèles? Devrais-je utiliser void * et tout jeter statiquement? Ou devrais-je aller avec une définition de classe vide & "Element &"; cela ne déclare rien et l'utilise comme classe de niveau supérieur dans & "Element &"; hiérarchie?

L’une des raisons pour lesquelles j’aime le débordement de pile est que beaucoup de réponses fournissent des informations intéressantes sur d’autres approches que je n’avais même pas envisagées. Merci d’avance pour vos idées et vos commentaires.

Était-ce utile?

La solution

Ne pouvez-vous pas avoir une classe conteneur racine contenant des éléments:

template <typename T>
class Container
{
public: 

   // You'll likely want to use shared_ptr<T> instead.
   virtual void push(T *element) = 0;
   virtual T *pop() = 0;
   virtual void InvokeSomeMethodOnAllItems() = 0;
};

template <typename T>
class List : public Container<T>
{
    iterator begin();
    iterator end();
public:
    virtual void push(T *element) {...}
    virtual T* pop() { ... }
    virtual void InvokeSomeMethodOnAllItems() 
    {
       for(iterator currItem = begin(); currItem != end(); ++currItem)
       {
           T* item = *currItem;
           item->SomeMethod();
       }
    }
};

Ces conteneurs peuvent ensuite être passés sous forme polymorphe:

class Item
{
public:
   virtual void SomeMethod() = 0;
};

class ConcreteItem
{
public:
    virtual void SomeMethod() 
    {
        // Do something
    }
};  

void AddItemToContainer(Container<Item> &container, Item *item)
{
   container.push(item);
}

...

List<Item> listInstance;
AddItemToContainer(listInstance, new ConcreteItem());
listInstance.InvokeSomeMethodOnAllItems();

Ceci vous donne l'interface conteneur de manière générique et sécurisée.

Si vous souhaitez ajouter des contraintes au type d'éléments pouvant être contenus, vous pouvez faire quelque chose comme ceci:

class Item
{
public:
  virtual void SomeMethod() = 0;
  typedef int CanBeContainedInList;
};

template <typename T>
class List : public Container<T>
{
   typedef typename T::CanBeContainedInList ListGuard;
   // ... as before
};

Autres conseils

Vous pouvez utiliser un conteneur standard de boost: : any si vous stockez des données vraiment arbitraires dans le conteneur.

On dirait plutôt que vous préférez quelque chose comme boost :: ptr_container où tout ce qui peut être stocké dans le conteneur doit provenir d'un type de base, et le conteneur lui-même ne peut vous donner que des références au type de base.

Le plus simple est de définir une classe de base abstraite appelée Container et de la sous-classer pour chaque type d’élément que vous souhaitez stocker. Vous pouvez ensuite utiliser n'importe quelle classe de collection standard (std::vector, std::list, etc.) pour stocker les pointeurs sur <=>. N'oubliez pas que puisque vous stockez des pointeurs, vous devez gérer leur allocation / désallocation.

Cependant, le fait qu’une seule collection soit nécessaire pour stocker des objets de types si différents est une indication que quelque chose ne va pas dans la conception de votre application. Il peut être préférable de revoir la logique métier avant de mettre en œuvre ce conteneur super générique.

Le polymorphisme et les modèles fonctionnent très bien ensemble, si vous les utilisez correctement.

Quoi qu'il en soit, je comprends que vous souhaitiez stocker un seul type d'objets dans chaque instance de conteneur. Si c'est le cas, utilisez des modèles. Cela vous évitera de stocker le mauvais type d'objet par erreur.

En ce qui concerne les interfaces de conteneur: en fonction de votre conception, vous pourrez peut-être également les modéliser, puis utiliser des méthodes telles que void push(T* new_element). Pensez à ce que vous saurez à propos de l'objet lorsque vous souhaitez l'ajouter à un conteneur (d'un type inconnu). D'où viendra l'objet en premier lieu? Une fonction qui retourne void*? Savez-vous que ce sera comparable? Au moins, si toutes les classes d’objets stockées sont définies dans votre code, vous pouvez les faire hériter d’un ancêtre commun, par exemple Storable, et utiliser Storable* au lieu de void push(Storable* new_element).

Maintenant, si vous voyez que les objets seront toujours ajoutés à un conteneur par une méthode telle que <=>, alors il n'y aura vraiment aucune valeur ajoutée à faire du conteneur un modèle. Mais vous saurez alors qu’il faut stocker Storables.

Tout d’abord, les modèles et le polymorphisme sont des concepts orthogonaux et ils fonctionnent bien ensemble. Ensuite, pourquoi voulez-vous une structure de données spécifique? Qu'en est-il des structures de données STL ou boost (plus précisément pointeur contenant ) ne fonctionne pas pour vous.

Compte tenu de votre question, il semblerait que vous utiliseriez mal l'héritage dans votre cas. Il est possible de créer & Quot; contraintes . " sur ce qui se passe dans vos conteneurs, en particulier si vous utilisez des modèles. Ces contraintes peuvent aller au-delà de ce que votre compilateur et votre éditeur de liens vous donneront. C'est en fait plus gênant pour ce genre de chose avec l'héritage et les erreurs sont plus susceptibles de rester pour l'exécution.

Avec le polymorphisme, il vous reste essentiellement une classe de base pour le conteneur et des classes dérivées pour les types de données. La classe de base / les classes dérivées peuvent avoir autant de fonctions virtuelles que nécessaire, dans les deux sens.

Bien sûr, cela voudrait dire que vous auriez également besoin de placer les types de données primitifs dans des classes dérivées. Si vous souhaitez reconsidérer l'utilisation de modèles dans leur ensemble, c'est ici que j'utiliserais les modèles. Créez une classe dérivée à partir de la base, qui est un modèle, et utilisez-la pour les types de données primitifs (et pour les autres pour lesquels vous n'avez pas besoin de plus de fonctionnalités que celles fournies par le modèle).

N'oubliez pas que vous pourriez vous simplifier la vie en choisissant des types pour chaque type basé sur un modèle, en particulier si vous devez ensuite transformer l'un d'eux en classe.

Vous pouvez également consulter la vérification du concept Boost Bibliothèque (BCCL) conçue pour fournir des contraintes sur les paramètres de modèle des classes basées sur des modèles, vos conteneurs dans ce cas.

Et pour répéter ce que d’autres ont dit, je n’ai jamais eu de problème à mélanger polymorphisme et modèles, et j’ai réalisé des tâches assez complexes avec eux.

Vous n'avez pas besoin d'abandonner des interfaces de type Java et d'utiliser également des modèles. La suggestion de Josh d'une Le modèle de base générique Container vous permettrait certainement de faire passer de manière polymorphe les conteneurs et leurs enfants, mais vous pourriez également implémenter des interfaces en tant que classes abstraites pour contenir les éléments contenus. Il n'y a aucune raison pour que vous ne puissiez pas créer une classe IComparable abstraite comme vous l'avez suggéré, de sorte que vous puissiez avoir une fonction polymorphe comme suit:

class Whatever
{
   void MyPolymorphicMethod(Container<IComparable*> &listOfComparables);
}

Cette méthode peut désormais prendre n'importe quel enfant de Container contenant une classe implémentant IComparable, ce qui la rend extrêmement flexible.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top