I would like to replace big switch with something more elegant.
class Base
{
public:
Base(void*data, int size);
virtu
Something like this should work:
#include <map>
#include <functional>
#include <memory>
typedef std::function< std::unique_ptr< Base >( void* data, int size ) > Factory;
std::map< char, Factory > factories =
{
{ 'a', []( void* data, int size ){ return std::make_unique<A>( data, size ); } },
{ 'b', []( void* data, int size ){ return std::make_unique<B>( data, size ); } }
};
char input = 'a';
void* data = 0;
int size = 0;
auto factory = factories.find( input );
if ( factory != factories.end() )
{
factory->second( data, size )->Something();
}
You just need to add a single line to the factories list for each class.
If you are using an enum with contiguous values starting from 0 then you can just use an array rather than a std::map
, e.g:
enum class Class
{
a,
b
};
Factory factories[] =
{
[]( void* data, int size ){ return std::make_unique<A>( data, size ); },
[]( void* data, int size ){ return std::make_unique<B>( data, size ); }
};
Class input = Class::a;
factories[static_cast<size_t>(input)]( data, size )->Something();
Find an implementation of static_for
, and it's simplicity itself:
using list = std::tuple<A, B, C, D, E, F, G, ...>;
const auto n = c - 'a';
static_for<std::tuple_size<list>()>([&](auto N){
if (n != N)
return;
using T = std::tuple_element_t<list, N>;
T obj(data, size);
obj.Something();
});
Further considerations:
If they all have the same polymorphic interface, you could decide to only use this for creating the object.
If you have holes in your range, if constexpr
and std::is_same
are your friends.
It might be better to use some dedicated typelist-type rather than std::tuple
, but this works in a pinch.
An unpolished, quick and dirty example-implementation for static_for()
:
template <std::size_t Is, class F>
void static_for_impl(F&& f, std::index_sequence<Is...>) {
f(std::integral_constant<std::size_t, Is>()), ...;
}
template <std::size_t N, class F>
void static_for(F&& f) {
static_for_impl(f, std::make_index_sequence<N>());
}
You can use a simple factory method to create objects by required type and constructor parameters as in following example. Don't forget the virtual destructor when using inheritance and virtual functions.
#include <memory>
class Base
{
public:
Base(void* data, int size) {};
virtual ~Base() {}
virtual void Something() = 0;
};
class A : public Base
{
public:
A(void* data, int size) : Base(data, size) {}
void Something() override {};
};
class B : public Base
{
public:
B(void* data, int size) : Base(data, size) {}
void Something() override {};
};
Base* MyFactory(char type, void* data, int size)
{
switch (type)
{
case 'a': return new A(data, size);
case 'b': return new B(data, size);
default:
return nullptr;
}
}
int main()
{
std::unique_ptr<Base> obj1(MyFactory('a', nullptr, 1));
obj1->Something();
std::unique_ptr<Base> obj2(MyFactory('b', nullptr, 1));
obj2->Something();
}
If the constructors are exactly the same and Something
methods are called similarly then you should be able to use templates like this:
template<typename T>
void DoSomething(void*data, int size){
T t(data, size);
t.Something();
}
..
{
switch(input){
case 'a': DoSomething<A>(..); break;
case 'b': DoSomething<B>(..); break;
}
}
you can use is_base_of if you want to validate that the template is a derived class of Base
.
Since you switch on an unknown variable (in this case a char
) I'm not sure how you would minimize the switch, unless following the pattern suggested by Alan Birtles.