C ++, как заменить конструктор switch? - PullRequest
2 голосов
/ 05 марта 2019

Я хотел бы заменить большой выключатель на что-то более элегантное.

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

Как видно из примера, классы A и B не отличаются снаружи. Я хотел бы найти способ исключить этот переключатель, который просто создает экземпляр правильного класса и вызывает для него тот же метод, в моем реальном коде этот переключатель имеет длину более 1000 строк, а классов гораздо больше, но я не могу найти какой-либо способ избавиться от этого.

В реальном коде у меня есть 2 перечисления вместо char, и есть больше переключателей, но я надеюсь, что проблема ясна.

Одной из моих идей было использование шаблонов в базовом классе, но я не нашел способа, как создать правильного потомка без этого огромного переключателя.

EDIT Я получаю пакет из сети и хочу разобрать его и обработать. Эти классы a, b, ... не имеют закрытых или открытых членов, базовый класс имеет только необработанный указатель на дату и общий указатель на сокет ответа (также из конструктора).

Я бы хотел, чтобы компилятор сгенерировал этот переключатель для меня с помощью шаблонов? или какого-либо другого механизма удаления этого повторяющегося исходного кода. Сейчас он все еще находится в стадии тестирования, но обрабатывает около 1000 пакетов в секунду, поэтому я не хочу удалять коммутатор и терять производительность при распределении и освобождении в куче.

Ответы [ 4 ]

4 голосов
/ 05 марта 2019

Найдите реализацию static_for и ее простоту:

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

Дополнительные соображения:

  1. Если все они имеют один и тот же полиморфный интерфейс, выможет решить использовать это только для создания объекта.

  2. Если у вас есть дыры в вашем диапазоне, if constexpr и std::is_same ваши друзья.

  3. Возможно, лучше использовать какой-то отдельный тип списка, а не std::tuple, но это работает в крайнем случае.

Неполированная, быстрая и грязная примерная реализация для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>());
}
1 голос
/ 05 марта 2019

Если конструкторы абсолютно одинаковы и методы Something вызываются аналогично, вы можете использовать шаблоны следующим образом:

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

вы можете использовать is_base_ofесли вы хотите проверить, что шаблон является производным классом Base.

Поскольку вы включаете неизвестную переменную (в данном случае char), я не уверен, как вы минимизируете переключение, если не будете следовать шаблону , предложенному Аланом Биртлзом .

1 голос
/ 05 марта 2019

Вы можете использовать простой фабричный метод для создания объектов по требуемому типу и параметрам конструктора, как в следующем примере. Не забывайте виртуальный деструктор при использовании наследования и виртуальных функций.

#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();
}
1 голос
/ 05 марта 2019

Примерно так должно работать:

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

Вам просто нужно добавить одну строку в список фабрик для каждого класса.

Если вы используете перечисление со смежными значениями, начинающимися с 0, тогда вы можете просто использовать массив вместо std::map, например:

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...