我希望能够检查 C++ 类的名称、内容(即成员及其类型)等信息。这里指的是本地 C++,而不是具有反射功能的托管 C++。 我知道 C++ 使用 RTTI 提供了一些有限的信息。哪些额外的库(或其他技术)可以提供这些信息?
我希望能够检查 C++ 类的名称、内容(即成员及其类型)等信息。这里指的是本地 C++,而不是具有反射功能的托管 C++。 我知道 C++ 使用 RTTI 提供了一些有限的信息。哪些额外的库(或其他技术)可以提供这些信息?
(int) x
,而不是写成int x
。以下是一些有用的宏来帮助使用类型表达式:#define REM(...) __VA_ARGS__
#define EAT(...)
// Retrieve the type
#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
// Strip off the type
#define STRIP(x) EAT x
// Show the type without parenthesis
#define PAIR(x) REM x
接下来,我们定义一个REFLECTABLE
宏来生成每个字段(以及字段本身)的数据。该宏将被这样调用:
REFLECTABLE
(
(const char *) name,
(int) age
)
因此,使用Boost.PP,我们迭代每个参数并生成以下数据:
// A helper metafunction for adding const to a type
template<class M, class T>
struct make_const
{
typedef T type;
};
template<class M, class T>
struct make_const<const M, T>
{
typedef typename boost::add_const<T>::type type;
};
#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
Self & self; \
field_data(Self & self) : self(self) {} \
\
typename make_const<Self, TYPEOF(x)>::type & get() \
{ \
return self.STRIP(x); \
}\
typename boost::add_const<TYPEOF(x)>::type & get() const \
{ \
return self.STRIP(x); \
}\
const char * name() const \
{\
return BOOST_PP_STRINGIZE(STRIP(x)); \
} \
}; \
fields_n
,它表示该类中可反射字段的数量。然后为每个字段专门化field_data
。还将reflector
类设置为友元,这样即使字段是私有的也可以访问它们。struct reflector
{
//Get field_data at index N
template<int N, class T>
static typename T::template field_data<N, T> get_field_data(T& x)
{
return typename T::template field_data<N, T>(x);
}
// Get the number of fields
template<class T>
struct fields
{
static const int n = T::fields_n;
};
};
struct field_visitor
{
template<class C, class Visitor, class I>
void operator()(C& c, Visitor v, I)
{
v(reflector::get_field_data<I::value>(c));
}
};
template<class C, class Visitor>
void visit_each(C & c, Visitor v)
{
typedef boost::mpl::range_c<int,0,reflector::fields<C>::n> range;
boost::mpl::for_each<range>(boost::bind<void>(field_visitor(), boost::ref(c), v, _1));
}
现在到了关键时刻,我们把所有东西都组合起来。以下是如何定义一个可反射的Person
类:
struct Person
{
Person(const char *name, int age)
:
name(name),
age(age)
{
}
private:
REFLECTABLE
(
(const char *) name,
(int) age
)
};
这里是一个通用的print_fields
函数,使用反射数据迭代字段:
struct print_visitor
{
template<class FieldData>
void operator()(FieldData f)
{
std::cout << f.name() << "=" << f.get() << std::endl;
}
};
template<class T>
void print_fields(T & x)
{
visit_each(x, print_visitor());
}
Person
类来演示如何使用 print_fields
的示例:int main()
{
Person p("Tom", 82);
print_fields(p);
return 0;
}
这将输出:
name=Tom
age=82
看啊,我们只用不到100行代码就在C++中实现了反射。
#define DETAIL_TYPEOF_INT2(tuple) DETAIL_TYPEOF_HEAD tuple
和#define DETAIL_TYPEOF_INT(...) DETAIL_TYPEOF_INT2((__VA_ARGS__))
,并将 TYPEOF(x) 的定义更改为 #define TYPEOF(x) DETAIL_TYPEOF_INT(DETAIL_TYPEOF_PROBE x,)
。 - Phenglei Kai有两种关于reflection
的说法。
template-tricks
在C++中可以实现这种检查。可以使用boost::type_traits
进行许多操作(如检查类型是否为整数)。要检查成员函数的存在性,请使用Templated check for the existence of a class member function? 。要检查某个嵌套类型是否存在,请使用普通的SFINAE 。如果您更想了解如何实现第一种方式,例如查看类有多少方法或获取类id的字符串表示形式,则恐怕没有标准的C++方法来实现此操作。您必须使用以下任一方法:
C++是以速度为重点设计的语言。如果您想要高级别的检查,就像C#或Java一样,那么没有办法不进行额外的工作。
members<T>
,返回 T 的所有成员列表。如果我们想要运行时反射(即将 RTTI 与反射混合使用),编译器仍将知道所有反射的基类型。很可能 members<T>(T&)
永远不会为 T=std::string 实例化,因此无需包含 std::string 或其派生类的 RTTI。 - MSalters在C++中,原生不支持反射。这很遗憾,因为它使得防御性测试变得困难。
有几种方法可以实现反射:
第一个链接看起来最有希望(使用clang的模块),第二个讨论了一些技术,第三个是使用gcc的不同方法:
编辑于13/08/17:
自原帖发布以来,关于反射的潜在进展已经有了一些。以下提供了更多细节和对各种技术和状态的讨论:
然而,除非社区对C++中的反射支持表现出更多兴趣,否则在不久的将来,标准化的反射方法在C++中的前景并不乐观。
以下是根据上次C++标准会议的反馈所得到的当前状态的详细信息:
编辑于2017年12月13日
反射似乎正在朝着C++ 20或更可能是TSR的方向发展。然而,进展缓慢。
编辑于2018年09月15日
一份草案TS已经发送给各国机构进行投票。
文本可以在这里找到:https://github.com/cplusplus/reflection-ts
编辑于2019年11月7日
反射TS已经完成并在2019年夏季进行评论和投票。
元模板编程方法将被一个更简单的编译时代码方法所取代(在TS中没有反映出来)。
编辑于2020年10月2日
这里有一个关于在Visual Studio中支持反射TS的请求:
作者大卫·桑克尔在TS上的讲话:2020年3月17日编辑
反思工作正在进行中。可以在此处找到《2020年2月布拉格ISO C++委员会之行报告》:
C++23的相关内容详情可以在这里找到(包括有关反射的简短部分):2020年6月4日编辑
Jeff Preshing发布了一个名为“Plywood”的新框架,其中包含了一种运行时反射机制。更多详细信息可以在这里找到:
这些工具和方法看起来是迄今为止最精细和最容易使用的。
编辑于2020年7月12日
Clang实验性反射分支:https://github.com/lock3/meta/wiki
有趣的反射库,使用Clang工具库提取信息,实现简单的反射,无需添加宏:https://github.com/chakaz/reflang
编辑于2021年2月24日
一些额外的Clang工具方法:
编辑于2021年8月25日
在YouTube上有一个ACCU的在线讲座https://www.youtube.com/watch?v=60ECEc-URP8,非常值得一听。它讨论了关于标准的当前提案以及基于clang的实现。
请参阅:
编辑于2023年5月31日 感谢@ilciavo指出这个库。
Boost Describe似乎是一种有趣的反射方法。它结合了宏和一个良好支持的库,具有良好的文档。
请参阅:
编辑于2023年11月13日
根据Herb Sutter的报道,最近的C++会议上关于Reflection的讨论似乎有所进展。
这里是引用的论文: 看起来我们可能会在C++26中获得静态反射。我希望拥有一匹小马,但小马是不免费的。 :-p
http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI 是你将得到的内容。像你所想的那样具有完全描述性的元数据在运行时是不会为C ++默认提供的。
这些信息确实存在,但并不以您所需的格式存在,只有在导出类时才能访问。这适用于Windows系统,我不知道其他平台。例如,使用存储类说明符:
class __declspec(export) MyClass
{
public:
void Foo(float x);
}
这将使编译器将类定义数据构建到DLL/Exe中。但它的格式不是您可以轻松用于反射的格式。
在我的公司,我们构建了一个库来解释此元数据,并允许您在不向类本身插入额外的宏等内容的情况下反射一个类。它允许像以下这样调用函数:
MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);
这实际上做了以下操作:
instance_ptr->Foo(1.331);
Invoke(this_pointer,...) 函数具有可变参数。显然,通过这种方式调用函数,你会绕过const保护等机制,因此这些方面都被实现为运行时检查。
我相信语法可以改进,目前它只在Win32和Win64上工作。我们发现它非常有用,可以自动创建C++类的GUI界面,创建属性,将数据流传输到和从XML中,而且不需要从特定基类派生。如果有足够的需求,也许我们可以完善它并发布出来。
__declspec(dllexport)
,如果你在构建过程中启用了 .map 文件的创建,那么你可以从该文件中检索信息。 - Orwellophile我建议使用Qt。
它提供开源许可证和商业许可证。
你需要看一下自己想要做什么,如果RTTI能够满足你的需求。我已经为某些非常特定的目的实现了自己的伪反射。例如,我曾经希望能够灵活地配置模拟输出内容。这需要在将要输出的类中添加一些样板代码:
namespace {
static bool b2 = Filter::Filterable<const MyObj>::Register("MyObject");
}
bool MyObj::BuildMap()
{
Filterable<const OutputDisease>::AddAccess("time", &MyObj::time);
Filterable<const OutputDisease>::AddAccess("person", &MyObj::id);
return true;
}
第一次调用将此对象添加到过滤系统中,该系统调用BuildMap()
方法来确定可用的方法。
然后,在配置文件中,您可以这样做:
FILTER-OUTPUT-OBJECT MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1 person == 1773
FILTER-CLAUSE-2 time > 2000
通过一些涉及boost
的模板魔法,这将在运行时(读取配置文件时)转换为一系列方法调用,所以它相当高效。我不建议你这样做,除非你真的需要,但是,当你这样做时,你可以做一些很酷的事情。