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?
5 Answers
Reset to default 6Maybe 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.
std::variant<ProgramA , ProgramB>
might be an option (possibly with pointer). – Jarod42 Commented Mar 19 at 15:27std::vector<std::unique_ptr<Base>>
for this, or you will end up with memory leaks – Pepijn Kramer Commented Mar 19 at 15:41Program
! – Marek R Commented Mar 19 at 15:50...
, 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