使用带有C结构的C ++模板进行内省?
我正在用C ++做一些其他用C语言编写的公司(使用C不是我的选项:()。它们有许多非常相似的数据结构(即它们都有字段)比如“名字”,“地址”等等。但是,无论出于何种原因,他们都没有一个共同的结构,以此为基础做任何其他事情(做任何事情)。无论如何,我需要做一个全系统的分析内存中的这些结构,并将它们全部放入表中。不是太糟糕,但表必须包含所有变量的所有字段的条目,即使它们没有字段(结构b可能)有字段“latency”,但struct a没有 – 在表中,a的每个实例的条目必须有一个“latency”的空条目。
所以,我的问题是,有没有办法在运行时确定传递给模板函数的结构是否具有特定字段? 或者我是否必须为我编写一些黑魔法宏? (问题基本上是我不能使用模板专业化)
谢谢! 如果您有任何疑问,请随时提出!
这是我在想什么的嗤之以鼻……
struct A { char name[256]; int index; float percision; }; struct B { int index; char name[256]; int latency; }; /* More annoying similar structs... note that all of the above are defined in files that were compiled as C - not C++ */ struct Entry { char name[256]; int index; float percision; int latency; /* more fields that are specific to only 1 or more structure */ }; template struct Entry gatherFrom( T *ptr ) { Entry entry; strcpy( entry.name, ptr->name, strlen( ptr->name ) ); entry.index = ptr->index; /* Something like this perhaps? */ entry.percision = type_contains_field( "percision" ) ? ptr->percision : -1; } int main() { struct A a; struct B b; /* initialization.. */ Entry e = gatherFrom( a ); Entry e2 = gatherFrom ( b ); return 0; }
用C写的其他一切(使用C不是我的选项:()。
首先,我想引用一下Linus Torvalds对此问题的看法:
From: Linus Torvalds linux-foundation.org> Subject: Re: [RFC] Convert builin-mailinfo.c to use The Better String Library. Newsgroups: gmane.comp.version-control.git Date: 2007-09-06 17:50:28 GMT (2 years, 14 weeks, 16 hours and 36 minutes ago) C++ is a horrible language. It's made more horrible by the fact that a lot of substandard programmers use it, to the point where it's much much easier to generate total and utter crap with it. Quite frankly, even if the choice of C were to do *nothing* but keep the C++ programmers out, that in itself would be a huge reason to use C.
http://harmful.cat-v.org/software/c++/linus
它们有许多非常相似的数据结构(即,它们都有诸如“名称”,“地址”等字段。但是,无论出于什么原因,它们都没有一个共同的结构,它们用来基于其他所有东西。 (做任何事情)。
他们可能有非常合理的理由。 将公共字段放入单个基础结构(类)可能听起来像个好主意。 但是如果你想对其中一个结构应用重大更改(替换某些字段,更改类型等)而使其余结构保持完整,则会使事情变得非常困难。 OOP当然不是一种真正的做事方式。
所以,我的问题是,有没有办法在运行时确定传递给模板函数的结构是否具有特定字段?
不,这是不可能的。 无论是在C语言还是在C ++中,因为在创建二进制文件时会丢弃有关类型的所有信息。 在C或C ++中既没有反思也没有内省。 好吧,从技术上讲,编译器发出的调试信息确实提供了这些信息,但是没有语言内置function来访问它。 此类调试信息也依赖于在编译时执行的分析,而不是在运行时。 C ++有RTTI,但这只是一个非常粗略的系统来识别实例关闭的类。 它对类或结构成员没有帮助。
但是你为什么还要关心在运行时呢?
无论如何,我需要对内存中的这些结构进行系统范围的分析,并将其全部分解到表中。
你真的很高兴你必须分析C而不是C ++。 因为C真的非常容易解析(与C ++非常难以解析,主要是因为那些模板模板)。 特别是结构。 我只是编写一个简单的小脚本,它从C源中提取所有结构定义。 但是,由于结构体大小不变,因此它们通常包含指向动态分配数据的指针。 除非你想修改你的分配器,我认为最简单的分析方法是挂钩调试器并记录指针分配给struct成员的每个唯一对象的内存使用情况。
您可以在编译时执行此操作,而无需触及原始结构的源:
#include #include #include struct A { char name[256]; int index; float percision; }; struct B { int index; char name[256]; int latency; }; struct Entry { char name[256]; int index; float percision; int latency; /* more fields that are specific to only 1 or more structure */ }; inline std::ostream & operator<<(std::ostream & os, Entry const & e) { return os << e.name << "{" << e.index << ", " << e.percision << ", " << e.latency << "}"; } template inline void assign(T & dst, T const & src) { dst = src; } template inline void assign(char (&dst)[N], char const (&src)[N]) { memcpy(dst, src, N); } #define DEFINE_ENTRY_FIELD_COPIER(field) \ template \ inline \ decltype(T::field, true) copy_##field(T const * t, Entry & e) { \ assign(e.field, t->field); \ return true; \ } \ \ inline \ bool copy_##field(void const *, Entry &) { \ return false; \ } DEFINE_ENTRY_FIELD_COPIER(name) DEFINE_ENTRY_FIELD_COPIER(index) DEFINE_ENTRY_FIELD_COPIER(percision) DEFINE_ENTRY_FIELD_COPIER(latency) template Entry gatherFrom(T const & t) { Entry e = {"", -1, std::numeric_limits::quiet_NaN(), -1}; copy_name(&t, e); copy_index(&t, e); copy_percision(&t, e); copy_latency(&t, e); return e; } int main() { A a = {"Foo", 12, 1.2}; B b = {23, "Bar", 34}; std::cout << "a = " << gatherFrom(a) << "\n"; std::cout << "b = " << gatherFrom(b) << "\n"; }
DEFINE_ENTRY_FIELD_COPIER()
宏为要提取的每个字段定义一对重载函数。 一个重载( copy_##field(T const * t, …)
,它变为copy_name(T const * t, …)
, copy_index(T const * t, …)
等)将其返回类型定义为decltype(T::field, true)
,如果T
有一个名为name
, index
等的数据成员,则解析为bool
类型。如果T
没有这样的字段,则替换失败,而不是导致编译时错误,简单地将重载视为不存在(这称为SFINAE ),并且调用因此解析为第二个重载, copy_##field(void const * t, …)
,它接受任何类型的第一个参数什么都不做
笔记:
-
因为此代码在编译时解决了重载,
gatherFrom()
是最佳的,因为生成的gatherFrom()
二进制代码看起来好像是你手动调整它的A
:Entry handCraftedGatherFromA(A const & a) { Entry e; e.latency = -1; memcpy(_result.name, a.name, sizeof(a.name)); e.index = a.index; e.percision = a.percision; return e; }
在带有
-O3
g ++ 4.8下,gatherFrom()
和handCraftedGatherFromA()
生成相同的代码:pushq %rbx movl $256, %edx movq %rsi, %rbx movl $-1, 264(%rdi) call _memcpy movss 260(%rbx), %xmm0 movq %rax, %rcx movl 256(%rbx), %eax movss %xmm0, 260(%rcx) movl %eax, 256(%rcx) movq %rcx, %rax popq %rbx ret
不幸的是,Clang 4.2的
gatherFrom()
表现不佳; 它冗余地将整个条目初始化为零。 所以这不是所有的玫瑰,我想。通过使用NRVO ,两个版本都避免在返回时复制
e
。 但是,我应该注意,两个版本都会通过使用输出参数而不是返回值来保存一个操作码(movq %rcx, %rax
)。 -
copy_…()
函数返回bool
结果,指示是否发生了复制。 目前尚未使用它,但可以使用它,例如,将int Entry::validFields
定义为指示填充了哪些字段的位掩码。 -
宏不是必需的; 这只是为了DRY 。 必不可少的成分是SFINAE的使用。
-
assign()
重载也不是必需的。 他们只是避免使用不同的几乎相同的宏来处理char数组。 -
上面的代码依赖于C ++ 11的decltype关键字。 如果你使用的是较旧的编译器,那就太麻烦了,但仍然可行。 我设法提出的最干净的解决方案如下。 它的C ++ 98符合并且仍然基于SFINAE原则:
template
struct EnableCopy { typedef T type; }; #define DEFINE_ENTRY_FIELD_COPIER(field, ftype) \ template \ inline \ typename EnableCopy ::type \ copy_##field(T const * t, Entry & e) { \ copy_value(e.field, t->field); \ return true; \ } \ \ inline \ bool copy_##field(void const *, Entry &) { \ return false; \ } DEFINE_ENTRY_FIELD_COPIER(name , char[256]); DEFINE_ENTRY_FIELD_COPIER(index , int); DEFINE_ENTRY_FIELD_COPIER(percision, float); DEFINE_ENTRY_FIELD_COPIER(latency , int); 你还必须放弃C ++ 11的可移植
std::numeric_limits
并使用一些技巧(::quiet_NaN() 0.0f/0.0f
似乎工作)或选择另一个魔术值。
是的,这一点都不难。 只需将A
和Entry
放在一个对象中,并使Entry
成为二等公民:
void setDefaultValues(Entry*); // You should be able to provide these. struct Entry { int x; int y; }; struct Indirect : public Entry { }; template struct EntryOr : public T, Indirect { setDefaultValues(this); }; // From C code struct A { int x; } int main() { EntryOr foo; foo.x = 5; // A::x std::cout << foo.x << foo.y; // Prints A::x and Entry::y }
(链接)