5 votes

Un vecteur pour les différentes classes

J'ai une classe avec un vecteur que j'aimerais remplir avec l'un des deux types de classe, sélectionné par l'utilisateur. Appelons mes classes option1 et option2

Ce que j'aimerais faire c'est quelque chose comme

class storage_class 
{
public:
    storage_class(int sel, int n)
    {
        if(sel == 1)
           for(int i = 0; i < n; i++) 
               my_store.push_back(std::make_unique<option1>());    
        else if(sel == 2)
           for(int i = 0; i < n; i++)
               my_store.push_back(std::make_unique<option2>());
    }

private:
    // Something like this but that actually works
    std::vector<T> my_store;
};

J'aimerais ensuite l'utiliser comme ceci, ou quelque chose de similaire, afin qu'il ne soit pas nécessaire de modifier cette utilisation en fonction de l'option choisie.

int main()
{
    storage_class store(1);

    int n_iterations = 4;

    for(int i = 0; i < n_iterations; i++)
    {
        store.my_store[i]->create_data();
    }
}

Les classes option1 et option2 seront des simulations mathématiques qui vont créer des données et elles-mêmes stocker ces données dans un vecteur qui sont membres de la classe.

Je veux stocker plusieurs instances de l'une ou l'autre option dans un vecteur et les manipuler à partir de là. Je peux utiliser C++17.

3voto

Klaus Points 2958

Comme vous utilisez c++17, vous pouvez tout simplement utiliser un fichier std::variant comme type pour le conteneur qui lui-même peut garder tous les types que vous voulez avoir.

Ejemplo:

class A { public: void Do() { std::cout << "A::Do" << std::endl; } };
class B { public: void Go() { std::cout << "B::Go" << std::endl; } };

template<class... Ts> struct funcs : Ts... { using Ts::operator()...; };
template<class... Ts> funcs(Ts...) -> funcs<Ts...>;

int main()
{
    std::vector<std::variant<A,B>> vec;
    vec.push_back(A{});
    vec.push_back(B{});

    for ( auto& el: vec)
    {
        std::visit( funcs{ [](A& a){ a.Do(); }, [](B& b) { b.Go(); } }, el);
    }
}

Sortie :

A::Do
B::Go

Les classes sont totalement indépendantes et les méthodes peuvent être simplement appelées avec la fonction std::visit et en passant un objet appelable ici. Je fournis un simple funcs qui rassemble simplement toutes les entités appelables pour simplifier l'interface de l'appel à différentes méthodes de différentes classes non liées ici.

Comme std::variant est une sorte d'union étiquetée, elle a besoin du stockage pour le plus grand type que vous avez utilisé. Si cela gaspille trop de mémoire, vous pouvez stocker un pointeur vers l'instance à la place, peut-être avec std::unique_ptr o std::shared_ptr si vous souhaitez une assistance pour la gestion de la mémoire ;)

2voto

Matt Points 9560

La méthode standard consiste à faire option1 y option2 des classes dérivées d'un base_class ce qui semble cohérent avec votre échantillon main() . Utilisation d'un générique Factory Voici un exemple de modèle de classe :

#include <functional>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>

// Generic Factory class template
template<typename K,typename T,typename... Ts>
class Factory
{
    using Map = std::unordered_map<K, std::function<std::unique_ptr<T>(Ts...)>>;
    const Map mMap;
  public:
    Factory(Map&& map):mMap(std::move(map)) { }
    std::unique_ptr<T> operator()(const K& key, Ts... args) const
    {
        const typename Map::const_iterator itr = mMap.find(key);
        return itr == mMap.cend() ? nullptr : itr->second(std::forward<Ts>(args)...);
    }
};

class base_class
{
  public:
    virtual void create_data() = 0;
};

class option1 : public base_class
{
  public:
    void create_data() override
    {
        std::cout << "I'm option1." << std::endl;
    }
};

class option2 : public base_class
{
  public:
    void create_data() override
    {
        std::cout << "I'm option2." << std::endl;
    }
};

class storage_class 
{
    using SimulationFactory = Factory<int,base_class>; // Optionally add constructor parameter types
    const SimulationFactory simulation_factory; // This can be made static const.
public:
    storage_class(int sel, int n)
    :   simulation_factory(
            { { 1, []() { return std::make_unique<option1>(); } }
            , { 2, []() { return std::make_unique<option2>(); } }
            })
    {
        for (int i = 0; i < n; i++) 
            my_store.push_back(simulation_factory(sel));
    }

    std::vector<std::unique_ptr<base_class>> my_store;
};

int main()
{
    int n_iterations = 4;

    storage_class store(1, n_iterations);

    for(int i = 0; i < n_iterations; i++)
    {
        store.my_store[i]->create_data();
    }
}

Cela a été compilé pour moi sous linux en utilisant g++ -std=c++17 main.cc .

Il y a des améliorations qui peuvent être faites à ce code, mais j'ai copié votre main() afin d'illustrer la ou les idées de base. J'espère que cela vous aidera.


Edit 21 Sept 2018 - Exemple de passage de paramètres dans les constructeurs.

Fichier : factory.h

#pragma once

#include <functional>
#include <memory>
#include <unordered_map>

// Generic Factory class template
template<typename K,typename T,typename... Ts>
class Factory
{
    using Map = std::unordered_map<K, std::function<std::unique_ptr<T>(Ts...)>>;
    const Map mMap;
  public:
    Factory(Map&& map):mMap(std::move(map)) { }
    std::unique_ptr<T> operator()(const K& key, Ts... args) const
    {
        const typename Map::const_iterator itr = mMap.find(key);
        return itr == mMap.cend() ? nullptr : itr->second(std::forward<Ts>(args)...);
    }
};

Fichier : main.cc

#include "factory.h"

#include <iostream>
#include <string>
#include <vector>

class base_class
{
  public:
    virtual void create_data() = 0;
};

class option1 : public base_class
{
    const double mD;
  public:
    option1(double d)
    :   mD(d)
    { }
    void create_data() override
    {
        std::cout << "I'm option1: mD("<<mD<<')' << std::endl;
    }
};

class option2 : public base_class
{
    const double mD;
  public:
    option2(double d)
    :   mD(d)
    { }
    void create_data() override
    {
        std::cout << "I'm option2: mD("<<mD<<')' << std::endl;
    }
};

class storage_class 
{
    using SimulationFactory = Factory<int,base_class,double>; // Optionally add constructor parameter types
    const SimulationFactory simulation_factory; // This can be made static const.
public:
    storage_class(int sel, int n)
    :   simulation_factory(
            { { 1, [](double d) { return std::make_unique<option1>(d); } }
            , { 2, [](double d) { return std::make_unique<option2>(d); } }
            })
    {
        for (int i = 0; i < n; i++) 
            my_store.push_back(simulation_factory(sel,static_cast<double>(i)));
    }

    std::vector<std::unique_ptr<base_class>> my_store;
};

int main()
{
    int n_iterations = 4;

    storage_class store1(1, n_iterations);
    storage_class store2(2, n_iterations);

    for(int i = 0; i < n_iterations; i++)
    {
        store1.my_store[i]->create_data();
        store2.my_store[i]->create_data();
    }
}

Sortie :

I'm option1: mD(0)
I'm option2: mD(0)
I'm option1: mD(1)
I'm option2: mD(1)
I'm option1: mD(2)
I'm option2: mD(2)
I'm option1: mD(3)
I'm option2: mD(3)

2voto

Thomas Points 2676

Voici un exemple qui tente de rester aussi proche que possible de votre exemple en utilisant un paramètre de modèle sur la classe storage_class . Voir la version de travail aquí . J'ai ajouté seulement option1 et a fait du membre my_store public comme vous y accédez dans votre main fonction.

#include <memory>
#include <vector>
#include <iostream>

struct option1{
    void create_data(){ std::cout << "created\n"; }
};

template<typename T>
class storage_class 
{
public:
    storage_class(int n)
    {
       for(int i = 0; i < n; i++) 
           my_store.push_back(std::make_unique<T>());    
    }

    std::vector<std::unique_ptr<T>> my_store;
};

int main()
{
    storage_class<option1> store(4);

    int n_iterations = 4;

    for(int i = 0; i < n_iterations; i++)
    {
        store.my_store[i]->create_data();
    }
}

une autre option serait d'utiliser std::variant . Voir la version de travail aquí .

#include <memory>
#include <vector>
#include <variant>
#include <iostream>

struct option1{
    void create_data(){ std::cout << "created 1\n"; }
};

struct option2{
    void create_data(){ std::cout << "created 2\n"; }
};

class storage_class 
{
public:

    using option = std::variant<std::unique_ptr<option1>,std::unique_ptr<option2>>;

    storage_class(int sel, int n)
    {
        if(sel == 0)
           for(int i = 0; i < n; i++) 
               my_store.push_back(option(std::make_unique<option1>()));    
        else if(sel == 1)
           for(int i = 0; i < n; i++)
               my_store.push_back(option(std::make_unique<option2>()));
    }

    std::vector<option> my_store;
};

int main()
{
    storage_class store(1, 4);

    int n_iterations = 4;

    for(int i = 0; i < n_iterations; i++)
    {
        std::get<1>(store.my_store[i])->create_data();
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X