Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A vector for different classes

I have a class with a vector I'd like to fill with one of two types of class, selected by the user. Let's call my classes option1 and option2

What I'd like to do it something like

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;
};

Then I'd like to use it like this, or something similar, so there's no need to modify this usage dependent on the option chosen.

int main()
{
    storage_class store(1);

    int n_iterations = 4;

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

The classes option1 and option2 will be mathematical simulations that will be creating data and themselves store this data in a vector that are members of the class.

I want to store multiple instances of either option in a vector and then manipulate them from there. I can use C++17.

like image 756
Gar Avatar asked Sep 12 '18 17:09

Gar


3 Answers

As you have c++17 in use, you can simply use a std::variant as type for the container which itself can keep all types you want to have.

Example:

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);
    }
}

Output:

A::Do
B::Go

The classes are fully independent and the methods can be simply called with std::visit and passing a callable object herein. I provide a simple funcs implementation, which simply collect all callable entities to simplify to interface the call to different methods of different unrelated classes here.

As std::variant is some kind of a tagged union, it needs the storage for the biggest type you have in use. If this wastes to much memory, you can store a pointer to the instance instead, maybe with std::unique_ptr or std::shared_ptr if you like some assistance for memory management ;)

like image 78
Klaus Avatar answered Oct 07 '22 10:10

Klaus


Here is an example that tries to stay as close to your example as it can using a template parameter on class storage_class. See working version here. I've added only option1 and made the member my_store public as you access it in your main function.

#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();
    }
}

another option would be to use std::variant. See workign version here.

#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();
    }
}
like image 26
Thomas Avatar answered Oct 07 '22 08:10

Thomas


Standard way is to make option1 and option2 derived classes from a base_class which seems consistent with your sample main(). Using a generic Factory class template, here is an example:

#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();
    }
}

This compiled for me on linux using g++ -std=c++17 main.cc.

There are improvements that can be made to this code, but I copied your main() function in order to illustrate the basic idea(s). Hope that helps.


Edit 21 Sept 2018 - Example of how to pass parameters into constructors.

File: 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)...);
    }
};

File: 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();
    }
}

Output:

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)
like image 20
Matt Avatar answered Oct 07 '22 10:10

Matt