Обработка стертых данных во время выполнения - как не изобретать велосипед? - PullRequest
0 голосов
/ 31 октября 2018

Я работаю над кодом, который получает данные, которые выглядят так:

enum data_type { INT16 = 0, INT32, UINT64, FLOAT, TIMESTAMP };
struct buffer {
    data_type element_type;
    size_t    size; // in elements of element_type, not bytes
    void*     data;
}

(это упрощено; в действительности существует довольно много типов, больше полей в этой структуре и т. Д.)

Теперь я обнаружил, что пишу кучу служебного кода для «преобразования» значений перечисления в фактические типы и наоборот во время компиляции. Затем я понимаю, что мне нужно сделать то же самое, что и мне, во время выполнения, и с переменным числом буферов ... так что теперь, в дополнение к поиску значений и перечислению на основе признаков типа поиск типов на основе параметров шаблона - я пишу код, который ищет std::type_info s. Это какой-то беспорядок.

Но на самом деле - я должен не делать это. Он повторяется, и я абсолютно уверен, что заново изобретаю колесо - реализую то, что уже было написано много раз: компиляторы, СУБД, парсеры файлов данных, библиотеки сериализации и т. Д.

Что я могу сделать, чтобы минимизировать мои напрасные усилия в этом начинании?

Примечания:

  • Я получаю эти буферы во время выполнения и не могу просто стереть тип во время компиляции (например, используя type_traits).
  • Я не могу изменить API. Вернее, я мог изменить все, что хотел, в своем коде, но я все еще получаю данные в этом макете в памяти.
  • Я не просто беру такие буферы в качестве входных данных, мне также нужно создавать их как выходные.
  • Иногда мне нужно обрабатывать много разных буферов одновременно, даже разное их количество (например, foo(buffer* buffers, int num_buffers);.
  • Решения C ++ 11 предпочтительнее, чем решения более новой стандартной версии.
  • Я на самом деле много использую gsl, так что вы можете использовать его в своих ответах, если хотите. Что касается Boost - от этого может быть политически сложно зависеть, но для целей вопроса StackOverflow, я думаю, это нормально.

Ответы [ 4 ]

0 голосов
/ 31 октября 2018

Используйте boost::variant и gsl::span.

enum data_type { INT16 = 0, INT32, UINT64, FLOAT, TIMESTAMP };
struct buffer {
  data_type element_type;
  size_t    size; // in elements of element_type, not bytes
  void*     data;
};

template<class...Ts>
using var_span = boost::variant< gsl::span< Ts > ... >;

using buffer_span = var_span< std::int16_t, std::int32_t, std::uint64_t, float, ??? >;

buffer_span to_span( buffer buff ) {
  switch (buff.element_type) {
    case INT16: return gsl::span<std::int16_t>( (std::int16_t*)buff.data, buff.size );
    // etc
  }
}

теперь вы можете

auto span = to_span( buff );

, а затем перейдите к span для безопасного доступа к буферу данных.

Написание посетителей менее болезненно в из-за [](auto&&) лямбды, но выполнимо в .

Написание template<class...Fs> struct overloaded также может облегчить написание посетителей. Существует множество реализаций.

Если вы не можете использовать boost, вы можете конвертировать to_span в visit_span и заставить его принять посетителя.

Если вы не можете использовать gsl, написание собственного span тривиально.

visit_span( buff, overload(
  [](span<int16_t> span) { /* code */ },
  [](span<int32_t> span) { /* code */ },
  // ...
 ));

или

 struct do_foo {
   template<class T>
   void operator()(span<T> span) { /* code */ }
 };
 visit_span( buff, do_foo{captures} );
0 голосов
/ 31 октября 2018

Цель здесь должна состоять в том, чтобы как можно быстрее вернуться в систему типов C ++. Для этого должна быть одна центральная функция, которая переключается на основе (времени выполнения) data_type, а затем передает каждый случай на версию шаблона (времени компиляции).

Вы не указали, как выглядят связанные функции, но вот пример:

template<typename T>
struct TypedBuffer
{
  TypedBuffer(void* data, size_t elementCount) { /* ... */ }
  // ...
};

template<typename T>
void handleBufferTyped(void* data, size_t elementCount)
{
  TypedBuffer<T> buf(data, elementCount);
  // Do whatever you want - you're back in the type system.
}

void handleBuffer(buffer buf)
{
  switch (buf.element_type)
  {
  case INT16:     handleBufferTyped<int16_t>(buf.data, buf.size); break;
  case INT32:     handleBufferTyped<int32_t>(buf.data, buf.size); break;
  case UINT64:    handleBufferTyped<uint64_t>(buf.data, buf.size); break;
  case FLOAT:     handleBufferTyped<float>(buf.data, buf.size); break;
  case TIMESTAMP: handleBufferTyped<std::time_t>(buf.data, buf.size); break;
  }
}

При необходимости вы также можете иметь TypedBuffer наследовать от не шаблонного базового класса, чтобы вы могли полиморфно возвращаться из handleBuffer, но это смешивает много парадигм и, вероятно, ненужно.

0 голосов
/ 31 октября 2018

как не изобретать велосипед?

Просто используйте std::variant вместе с преобразованиями туда-сюда. По какой-то причине он находится в стандартной библиотеке.

Что касается изобретения колеса, посещение - это самый простой универсальный механизм для обработки стертых данных

enum data_type { INT16 = 0, INT32, UINT64, FLOAT, TIMESTAMP, size };

template<data_type d>
struct data
{
    using type = void;
};
template<>
struct data<INT16>
{
    using type = int16_t;
};
// and so on

template<data_type d>
using data_t = typename data<d>::type;


template<typename F, typename T>
void indirect(void* f, void* t, int n)
{
    (*(F*)f)((T*)t, n);
}

template<typename F, size_t... Is>
void visit_(F&& f, buffer* bufs, int n, std::index_sequence<Is...>)
{
    using rF = typename std::remove_reference<F>::type;
    using f_t = void(*)(void*, void*, int);
    static constexpr f_t fs[] = {indirect<rF, data_t<data_type(Is)>>...};
    for(int i = 0; i < n; i++)
        fs[bufs[i].element_type](&f, bufs[i].data, bufs[i].size);
}

template<typename F>
void visit(F&& f, buffer* bufs, int n)
{
    visit_(std::forward<F>(f), bufs, n, std::make_index_sequence<data_type::size>{});
}

std::index_sequence и друзья могут быть относительно легко реализованы в C ++ 11. Использовать как

struct printer
{
    template<typename T>
    void operator()(T* t, int n)
    {
        for(int i = 0; i < n; i++)
            std::cout << t[i] << ' ';
        std::cout << '\n';
    }
};

void foo()
{
    visit(printer{}, nullptr, 0);
}
0 голосов
/ 31 октября 2018

Похоже, для этого используются type_traits (https://en.cppreference.com/w/cpp/types).

По сути, вы определяете шаблонную структуру, по умолчанию она пуста, и вы специализируете ее для каждого имеющегося у вас перечисления. Затем в своем коде вы используете MyTypeTraits<MyEnumValue>::type, чтобы получить тип, связанный с желаемым перечислением.

И все определяется во время компиляции. Если вам нужна информация времени выполнения, вы всегда можете выполнить некоторую диспетчеризацию, основываясь на значении шаблона (например, если вы также храните enum).

...