web-dev-qa-db-fra.com

C++ Un fichier std :: vector contenant une classe de modèles de plusieurs types

J'ai besoin de stocker plusieurs types d'une classe de modèle dans un seul vecteur.

Par exemple, pour:

template <typename T>
class templateClass{
     bool someFunction();
};

J'ai besoin d'un vecteur qui stockera tout:

templateClass<int> t1;
templateClass<char> t2;
templateClass<std::string> t3;
etc

Autant que je sache, ce n'est pas possible. Si c'est le cas, quelqu'un pourrait-il dire comment?

Si ce n’est pas possible, quelqu'un pourrait-il expliquer comment faire le travail suivant?

En guise de solution de contournement, j'ai essayé d'utiliser une classe de base non modèle et d'en hériter la classe de modèle.

 class templateInterface{
     virtual bool someFunction() = 0;
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction();
 };

J'ai ensuite créé un vecteur pour stocker la classe de base "templateInterface":

std::vector<templateInterface> v;
templateClass<int> t;
v.Push_back(t);

Cela a généré l'erreur suivante:

error: cannot allocate an object of abstract type 'templateInterface'
note: because the following virtual functions are pure within 'templateInterface'
note: virtual bool templateInterface::someFunction()

Pour corriger cette erreur, la fonction dans templateInterface n'est pas un virtuel, mais un corps de fonction. Ce dernier est compilé, mais lors de l'appel de la fonction, le dépassement n'est pas utilisé mais le corps de la fonction virtuelle.

Par exemple:

 class templateInterface{
     virtual bool someFunction() {return true;}
 };

 template <typename T>
 class templateClass : public templateInterface{
     bool someFunction() {return false;}
 };

 std::vector<templateInterface> v;
 templateClass<int> i;
 v.Push_back(i);
 v[0].someFunction(); //This returns true, and does not use the code in the 'templateClass' function body

Existe-t-il un moyen de résoudre ce problème de sorte que la fonction remplacée soit utilisée ou existe-t-il une autre solution de contournement permettant de stocker plusieurs types de modèles dans un seul vecteur?

34
jtedit

Pourquoi votre code ne fonctionne pas:

L'appel d'une fonction virtuelle sur une valeur n'utilise pas de polymorphisme. Il appelle la fonction qui est définie pour le type de ce symbole exact tel que vu par le compilateur, pas le type d'exécution. Lorsque vous insérez des sous-types dans un vecteur du type de base, vos valeurs seront converties dans le type de base ("type slicing"), ce qui n'est pas ce que vous voulez. L'appel de fonctions sur celles-ci appellera maintenant la fonction telle que définie pour le type de base, car il ne s'agit pas de est de ce type.

Comment régler ceci?

Le même problème peut être reproduit avec cet extrait de code:

templateInterface x = templateClass<int>(); // Type slicing takes place!
x.someFunction();  // -> templateInterface::someFunction() is called!

Le polymorphisme ne fonctionne que sur un type pointeur ou référence. Il utilisera ensuite le type d'exécution de l'objet derrière le pointeur/la référence pour décider quelle implémentation appeler (en utilisant sa vtable).

La conversion des pointeurs est totalement "sûre" en ce qui concerne le découpage en caractères. Vos valeurs réelles ne seront pas du tout converties et le polymorphisme fonctionnera comme prévu.

Exemple, analogue à l'extrait de code ci-dessus:

templateInterface *x = new templateClass<int>();  // No type slicing takes place
x->someFunction();  // -> templateClass<int>::someFunction() is called!

delete x;  // Don't forget to destroy your objects.

Qu'en est-il des vecteurs?

Vous devez donc adopter ces modifications dans votre code. Vous pouvez simplement stocker pointeurs dans les types réels du vecteur, au lieu de stocker directement les valeurs.

Lorsque vous travaillez avec des pointeurs, vous devez également vous soucier de supprimer vos objets alloués. Pour cela, vous pouvez utiliser pointeurs intelligents qui s’occupe de la suppression automatiquement. unique_ptr est un type de pointeur intelligent de ce type. Il supprime la pointee chaque fois qu’elle sort du domaine ("propriété unique" - le domaine étant le propriétaire). En supposant que la durée de vie de vos objets est liée à la portée, voici ce que vous devriez utiliser:

std::vector<std::unique_ptr<templateInterface>> v;

templateClass<int> *i = new templateClass<int>();    // create new object
v.Push_back(std::unique_ptr<templateInterface>(i));  // put it in the vector

v.emplace_back(new templateClass<int>());   // "direct" alternative

Ensuite, appelez une fonction virtuelle sur l'un de ces éléments avec la syntaxe suivante:

v[0]->someFunction();

Assurez-vous de créer toutes les fonctions virtuelles qui devraient pouvoir être remplacées par des sous-classes. Sinon, leur version remplacée ne sera pas appelée. Mais puisque vous avez déjà introduit une "interface", je suis sûr que vous travaillez avec des fonctions abstraites.

Approches alternatives:

Une autre façon de faire ce que vous voulez est d'utiliser un type variante dans le vecteur. Il existe quelques implémentations de types de variantes, le Boost.Variant étant très populaire. Cette approche est particulièrement intéressante si vous n’avez pas de hiérarchie de types (par exemple, lorsque vous stockez des types primitifs). Vous utiliseriez alors un type de vecteur comme std::vector<boost::variant<int, char, bool>>

26
leemes

Le polymorphisme ne fonctionne que par le biais de pointeurs ou de références. Vous aurez besoin de la base sans gabarit. Au-delà, vous devrez décider Où vivront les objets réels dans le conteneur. S'ils sont tous des objets statiques (avec une durée de vie suffisante), utiliser simplement .__ un std::vector<TemplateInterface*> et insérer avec v.Push_back(&t1);, etc. Sinon, vous voudrez probablement prendre en charge le clonage et conserver les clones dans le vecteur : De préférence avec des conteneurs de pointeurs Boost, mais vous pouvez également utiliser std::shared_ptr

2
James Kanze

Les solutions données jusqu’à présent sont acceptables, mais sachez que si vous renvoyez le type de modèle autre que bool dans votre exemple, aucune de ces solutions n’aiderait, car les emplacements vtable ne pourraient pas être mesurés à l’avance. Du point de vue de la conception, il existe en fait des limites à l’utilisation d’une solution polymorphe orientée modèle.

2
Mário de Sá Vera

Si vous recherchez un conteneur pour stocker plusieurs types, vous devriez explorer variante boost à partir de la bibliothèque boost populaire.

1
jbo5112

Solution no. 1

Cette solution est inspirée du discours sur l'assaisonnement C++ de Sean Parent. Je recommande fortement de vérifier sur youtube. Ma solution simplifie un peu et la clé est de stocker objet dans la méthode elle-même.

Une seule méthode

Créez une classe qui invoquera une méthode d'objet stocké.

struct object {
    template <class T>
    object(T t)
    : someFunction([t = std::move(t)]() { return t.someFunction(); })
    { }

    std::function<bool()> someFunction;
};

Alors utilisez-le comme ça

std::vector<object> v;

// Add classes that has 'bool someFunction()' method
v.emplace_back(someClass());
v.emplace_back(someOtherClass());

// Test our vector
for (auto& x : v)
    std::cout << x.someFunction() << std::endl;

Plusieurs méthodes

Pour plusieurs méthodes, utilisez un pointeur partagé pour partager des objets entre les méthodes.

struct object {
    template <class T>
    object(T&& t) {
        auto ptr = std::make_shared<std::remove_reference_t<T>>(std::forward<T>(t));
        someFunction = [ptr]() { return ptr->someFunction(); };
        someOtherFunction = [ptr](int x) { ptr->someOtherFunction(x); };
    }

    std::function<bool()> someFunction;
    std::function<void(int)> someOtherFunction;
};

Autres types

Les types primitifs (tels que int, float, const char*) ou les classes (std::string etc.) peuvent être encapsulés de la même manière que la classe object mais se comportent différemment. Par exemple: 

struct otherType {
    template <class T>
    otherType(T t)
    : someFunction([t = std::move(t)]() {
            // Return something different
            return true;
        })
    { }

    std::function<bool()> someFunction;
};

Alors maintenant, il est possible d’ajouter des types qui n’ont pas de méthode someFunction.

v.emplace_back(otherType(17));      // Adding an int
v.emplace_back(otherType("test"));  // A string

Solution no. 2

Après quelques réflexions, ce que nous avons fait en premier dans la première solution est créé avec un tableau de fonctions appelables. Alors pourquoi ne pas simplement faire ce qui suit à la place.

// Example class with method we want to put in array
struct myclass {
    void draw() const {
        std::cout << "myclass" << std::endl;
    }
};

// All other type's behaviour
template <class T>
void draw(const T& x) {
    std::cout << typeid(T).name() << ": " << x << std::endl;
}

int main()
{
    myclass x;
    int y = 17;

    std::vector<std::function<void()>> v;

    v.emplace_back(std::bind(&myclass::draw, &x));
    v.emplace_back(std::bind(draw<int>, y));

    for (auto& fn : v)
        fn();
}

Conclusion

Solution no. 1 est définitivement une méthode intéressante qui ne nécessite ni héritage ni fonctions virtuelles. Et peut être utilisé pour d’autres tâches dans lesquelles vous devez stocker un argument de modèle à utiliser ultérieurement.

Solution no. 2, en revanche, est plus simple, plus flexible et constitue probablement un meilleur choix ici.

0
Vladimir Talybin