最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Creating a list of types that derive from abstract class - Stack Overflow

programmeradmin2浏览0评论

I have classes ProgramA and ProgramB that derive from abstract class Program.

I sum them up into an array std::vector<Program*> and I pick a random one from there that runs for awhile.

#include <vector>

class Program{
    public:
        virtual void doStuff() = 0; 
};

class ProgramA : public Program{
    public:
        void doStuff(){ }   
};

class ProgramB : public Program{
    public:
        void doStuff(){ }   
};

std::vector<Program*> pPrograms =
{
    new ProgramA 
   ,new ProgramB
};

My problem is that instances of both ProgramA and ProgramB exist all the time.

What I would like to do is that only one of them exist at a time.

Is it possible that, instead of listing instances of those types, to list just the types, from where I would then pick a random one using the new and delete operators?

I have classes ProgramA and ProgramB that derive from abstract class Program.

I sum them up into an array std::vector<Program*> and I pick a random one from there that runs for awhile.

#include <vector>

class Program{
    public:
        virtual void doStuff() = 0; 
};

class ProgramA : public Program{
    public:
        void doStuff(){ }   
};

class ProgramB : public Program{
    public:
        void doStuff(){ }   
};

std::vector<Program*> pPrograms =
{
    new ProgramA 
   ,new ProgramB
};

My problem is that instances of both ProgramA and ProgramB exist all the time.

What I would like to do is that only one of them exist at a time.

Is it possible that, instead of listing instances of those types, to list just the types, from where I would then pick a random one using the new and delete operators?

Share Improve this question edited Mar 19 at 21:39 Remy Lebeau 601k36 gold badges507 silver badges851 bronze badges asked Mar 19 at 15:17 Awed2Awed2 512 bronze badges 4
  • std::variant<ProgramA , ProgramB> might be an option (possibly with pointer). – Jarod42 Commented Mar 19 at 15:27
  • Side note, do not use a vector of raw pointers for this, use std::vector<std::unique_ptr<Base>> for this, or you will end up with memory leaks – Pepijn Kramer Commented Mar 19 at 15:41
  • Offtopic: missing virtual destructor in Program! – Marek R Commented Mar 19 at 15:50
  • fwiw, if you remove ..., add the missing include you have a complete compilable example. Its only tiny changes on your code, but its big if you consider that not everybody else who wants to use your code (eg to write an answer) will have to do it – 463035818_is_not_an_ai Commented Mar 19 at 16:11
Add a comment  | 

5 Answers 5

Reset to default 6

Maybe you want a factory function. You have to list all subclasses manually though. If you want to automate enumerating all derived classes, things get way more involved and I suppose you want a solution that readily works out of the box.

std::unique_ptr<Program> make_program(int select) {
    switch (select) {
        case 0: return std::make_unique<ProgramA>();
        case 1: return std::make_unique<ProgramB>();
    }
    throw std::runtime_error("invalid select");
}

Don't use raw new and delete or you will get memory leaks and worse. Use smart pointers. And your base class is missing a virtual destructor.

If you pass a random integer in [0,2) the factory will create the instance and return it.

template<class Base, class...Derived>
std::unique_ptr<Base> PickImplementation(std::size_t index) {
  using factory = std::unique_ptr<Base>(*)();
  const factory array[] = {
    +[]()->std::unique_ptr<Base>{ return std::make_unique<Derived>(); }...
  };
  if (index < sizeof...(Derived))
    return array[index]();
  else
    return nullptr;
}

now you just do this:

std::unique_ptr<Program> pProgram = PickImplementation<Program, ProgramA, ProgramB>( 1 );

you just have to add each derived type supported to the list after Program.

If you don't want to maintain a single central list, you can instead have a factory-registration system (where classes register themselves as Program factories), then pick which of them via some key (which you pass in when you register them).

But that is something you should only consider much later.

Live example.

Alternative solution which is more flexible (as OP complained about adding more types under the other answer):

using ProgramPtr = std::unique_ptr<Program>;

class RandomFactory {
public:

    RandomFactory(std::seed_seq seed)
        : randGen { seed }
    {
    }

    ProgramPtr createRandom()
    {
        std::uniform_int_distribution<size_t> dist { 0, programs.size() - 1 };
        return programs[dist(randGen)]();
    }

    void addFactory(std::function<ProgramPtr()> f)
    {
        programs.push_back(std::move(f));
    }

    template <typename T>
    void addProgramType()
    {
        addFactory([]() { return std::make_unique<T>(); });
    }

private:
    std::vector<std::function<ProgramPtr()>> programs;
    std::mt19937 randGen;
};

Live demo

It is convenient to gather all required types at a single location, for instance:

using alltypes = std::tuple<ProgramA,ProgramB,ProgramC>;

and from this (like 463035818_is_not_an_ai's answer), one can create an instance from a given index.

However, retrieving a type in a std::tuple requires to know the index at compile-time, so we need some trick for this. It is possible to define a generic runtime_selector function that does the job. So a possible answer is:

#include <tuple>
#include <memory>
#include <iostream>
#include <stdexcept>

////////////////////////////////////////////////////////////////////////////////
class Program{
    public:
        virtual ~Program() {};
        virtual void doStuff() = 0; 
};

class ProgramA : public Program{
    public:
        void doStuff(){ std::cout << "ProgramA::doStuff\n"; }
};

class ProgramB : public Program{
    public:
        void doStuff(){ std::cout << "ProgramB::doStuff\n"; }
};

class ProgramC : public Program{
    public:
        void doStuff(){ std::cout << "ProgramC::doStuff\n"; }
};

////////////////////////////////////////////////////////////////////////////////
template<template<int> typename FCT, int Nmax, int Pending=0>
constexpr auto runtime_selector (int n)
{
    if constexpr (Nmax > 0)
    {
        if (n==0)  {  return FCT<0+Pending> {} ();  }
        else       {  return runtime_selector <FCT, Nmax-1,Pending+1> (n-1);  }
    }
    else
    {
        // We throw an exception.
        throw std::runtime_error ("bad index");

        // Although we have thrown an exception, we must help the compiler in order to have consistent deduction auto type
        return FCT<0> {} ();
    }
}

////////////////////////////////////////////////////////////////////////////////

// We define a tuple that gathers at a single location all the derived types.
// That will be the only location where the types are referred.
using alltypes = std::tuple<ProgramA,ProgramB,ProgramC>;

// We define the functor that will create the instance.
template<int N> struct MakeProgram
{
    // We can get the required item in the tuple since we transformed the runtime choice into a static one.
    using type = std::tuple_element_t<N,alltypes>;

    std::unique_ptr<Program> operator() () const
    {
        return std::make_unique<type>();
    }
};

// A little helper for end user.
auto make_program (int idx)
{
    return runtime_selector<MakeProgram,std::tuple_size_v<alltypes>> (idx);
}

////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
    int idx = argc>=2 ? atoi(argv[1]) : 0;

    try  {
        make_program(idx)->doStuff();
    }
    catch (std::runtime_error& e)
    {
        std::cout << "exception: " << e.what() << "\n";
    }
}

Demo

With c++20, one could have the following:

auto make_program (int idx)
{
    auto fct = [] <int N> () -> std::unique_ptr<Program>
    {
        return std::make_unique<std::tuple_element_t<N,alltypes>>();
    };

    return runtime_selector <fct, std::tuple_size_v<alltypes>> (idx);
}

Demo

I'd prefer raw pointers in this case. This is supposed to run on a microcontroller, so efficiency is cruical.

Also i fot to mention that it has to run on C++11.

I have broken it down to this:

template <typename T, int runtimeSec>
Program* construct()
{
    return new T(runtimeSec);
}

// i list my Program types here now, also i added a parameter
const std::vector<Program* (*)()> Programs =
{
     construct<ProgramA,30>
    ,construct<ProgramB,15>
    ,construct<ProgramC,30>
};

Demo

It does what its supposed to do but i wonder if this is fine, i'm not that familiar with templates and function pointers.

发布评论

评论列表(0)

  1. 暂无评论