C++ 在运行时处理类型擦除的数据-如何不重新发明轮子?
我正在编写一些代码,这些代码获取的数据如下所示:C++ 在运行时处理类型擦除的数据-如何不重新发明轮子?,c++,c++11,reflection,runtime,compile-time,c++14,C++,C++11,Reflection,Runtime,Compile Time,C++14,我正在编写一些代码,这些代码获取的数据如下所示: 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; } (这被简化了;实际上,这个结构中还有很多类型、更多字段等。) 现在
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的代码。有点乱
但真的-我不应该这样做。这是重复的,我绝对相信我正在重新发明轮子——实现一些已经编写过很多次的东西:编译器、DBMS、数据文件解析器、序列化库等等
我能做些什么来减少我在这项工作上浪费的精力
注:
- 我在运行时得到这些缓冲区,不能在编译时取消擦除类型(例如,使用type_)
- 我无法更改API。或者更确切地说,我可以在我的代码中更改我想要的任何内容,但我仍然可以在内存中获取这个布局中的数据
- 我不只是将这些缓冲区作为输入,我还需要将它们作为输出生成
- 我有时需要同时处理许多不同的缓冲区,甚至是数量可变的缓冲区(例如,
foo(buffer*buffers,int num_buffers);
- C++11解决方案优于较新的标准版本
- 实际上我经常使用gsl,所以如果你愿意,你可以在你的答案中使用它。至于Boost——这在政治上可能很难依赖,但就StackOverflow问题而言,我想这没关系
MyTypeTraits::type
获取与所需枚举关联的类型
如果您需要运行时信息,您可以根据模板的值进行一些分派(例如,如果您也存储枚举)这里的目标应该是尽快返回到C++类型的系统中。为了做到这一点,应该有一个中央函数,它根据(运行时)<代码> DATAYTYPE < /COD>进行切换,然后将每个情况都交给(编译时)模板版本。 您尚未指出关联函数的外观,但以下是一个示例:
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(void*数据,大小\u t元素计数){/*…*/}
// ...
};
样板
void handleBufferTyped(void*数据、大小\u t元素计数)
{
类型Buffer buf(数据,元素计数);
//你想做什么就做什么——你又回到了打字系统。
}
空把手缓冲器(缓冲器buf)
{
开关(基本元件类型)
{
案例INT16:手柄类型化(基本数据,基本尺寸);中断;
案例INT32:手柄类型化(基本数据,基本尺寸);中断;
案例UINT64:手柄类型化(基本数据,基本尺寸);断裂;
箱体浮子:手柄式(基本数据,基本尺寸);断开;
案例时间戳:handleBufferTyped(基本数据,基本尺寸);break;
}
}
如果需要,您还可以让TypedBuffer
从非模板基类继承,这样您就可以从handleBuffer
多态返回,但这混合了很多范例,可能是不必要的
如何不重新发明轮子
简单地说,使用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>{});
}
使用
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
}
}
然后访问span类型以安全访问数据缓冲区
由于[](自动&&)lambdas,在中编写访问者不那么痛苦,但在中是可行的
编写template struct重载
也可以更容易地编写访问者
如果您不能使用boost
,您可以将转换为访问,并让访问者访问
如果您不能使用gsl
,那么编写自己的span
就很简单了
visit_span( buff, overload(
[](span<int16_t> span) { /* code */ },
[](span<int32_t> span) { /* code */ },
// ...
));
visit_span(buff,重载(
[](span){/*代码*/},
[](span){/*代码*/},
// ...
));
或
struct do\u foo{
样板
void运算符()(span){/*代码*/}
};
访问_span(buff,do_foo{captures});
如果你能使用c++17,你可能想看看std::variantYeah,我想指出的是,这可以简化为向量的变体。除非我遗漏了什么。@Chemistree:这没有帮助,因为我无法更改API(请参见编辑)@StoryTeller:请参见编辑。我的一个可能相关的老问题,不是关于变体:为了使用类型特征,您需要在编译时知道枚举值。我只在运行ti.e1时知道。我在问如何避免编写此类内容…2.我还需要翻译回。3.实际缓冲区比这更复杂,并且单开关不起作用。4.这适用于单个缓冲区。但我有多个函数,可以使用多个缓冲区,如果这还不够的话,可以使用可变数量的缓冲区的函数:void foo(buffer*buffers,int num\u buffers)
@einpoklum 1.回到类型系统正需要这种东西。2.根据你的要求,让每个TypedBuffer
都有一个到buffer
的转换函数是很简单的。但是,如果我们不知道它们之间有什么不同/相同,我们就无法为你提供好的代码/抽象你编写了一个不明确的“它更复杂”的代码。4.循环
auto span = to_span( buff );
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} );