C++ how to replace constructor switch?

守給你的承諾、 提交于 2019-12-20 02:56:38

问题


I would like to replace big switch with something more elegant.

class Base
{
public:
  Base(void*data, int size);
  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;
}
...

{
  char c = input;
  switch (c)
  {
    case 'a':
    {
      A obj(data, size);
      obj.Something();
      break;
    }
    case 'b':
    {
      B obj(data, size);
      obj.Something();
      break;
    }
    ...
  }
}

as you see in the example class A and B do not differ from outside. I would like to find a way to eliminate that switch that just instantiate correct class and call same method on it, in my real code that switch is over 1000 lines long, and there is much more classes, but I can not find any way to get rid of it.

In real code I have 2 enums instead of char, and there are more switches, but I hope problem is clear.

One of my ideas was use templates on base class, but I did not find a way how to instantiate correct child without that huge switch.

EDIT I receive packet from network and want to parse it and handle it. These classes a, b, ... do not have any private or public members, base class have only raw pointer to date, and shared pointer to response socket (also from constructor).

I would like the compiler to generate that switch for me with templates? or some other mechanic to remove that repetitive source code. Right now it is still in testing phase, but it handle around 1000 packets per second, so I do not want to remove switch and lose performance, on allocation and deallocation on the heap.


回答1:


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:

  1. If they all have the same polymorphic interface, you could decide to only use this for creating the object.

  2. If you have holes in your range, if constexpr and std::is_same are your friends.

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



回答2:


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.




回答3:


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



回答4:


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


来源:https://stackoverflow.com/questions/55005051/c-how-to-replace-constructor-switch

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!