我希望能够自省一个c++类的名称,内容(即成员及其类型)等。这里我说的是原生c++,而不是托管c++,托管c++有反射。我意识到c++使用RTTI提供了一些有限的信息。哪些附加库(或其他技术)可以提供这些信息?


当前回答

在c++中反射是非常有用的,如果你需要为每个成员运行一些方法(例如:序列化,哈希,比较)。我给出了通用的解决方案,语法非常简单:

struct S1
{
    ENUMERATE_MEMBERS(str,i);
    std::string str;
    int i;
};
struct S2
{
    ENUMERATE_MEMBERS(s1,i2);
    S1 s1;
    int i2;
};

其中ENUMERATE_MEMBERS是一个宏,稍后将描述(UPDATE):

假设我们已经为int和std::string定义了序列化函数,如下所示:

void EnumerateWith(BinaryWriter & writer, int val)
{
    //store integer
    writer.WriteBuffer(&val, sizeof(int));
}
void EnumerateWith(BinaryWriter & writer, std::string val)
{
    //store string
    writer.WriteBuffer(val.c_str(), val.size());
}

我们在“secret宏”附近有一个泛型函数;)

template<typename TWriter, typename T>
auto EnumerateWith(TWriter && writer, T && val) -> is_enumerable_t<T>
{
    val.EnumerateWith(write); //method generated by ENUMERATE_MEMBERS macro
}

现在你可以写

S1 s1;
S2 s2;
//....
BinaryWriter writer("serialized.bin");

EnumerateWith(writer, s1); //this will call EnumerateWith for all members of S1
EnumerateWith(writer, s2); //this will call EnumerateWith for all members of S2 and S2::s1 (recursively)

因此在结构定义中有ENUMERATE_MEMBERS宏,你可以构建序列化、比较、散列和其他东西,而不需要触及原始类型,唯一的要求是为每个枚举器(如BinaryWriter)实现每个类型的“EnumerateWith”方法,这是不可枚举的。通常你必须实现10-20个“简单”类型来支持项目中的任何类型。

这个宏在运行时创建/销毁结构的开销应该为零,并且T.EnumerateWith()的代码应该按需生成,这可以通过使其成为模板内联函数来实现,因此所有故事中唯一的开销是向每个结构添加ENUMERATE_MEMBERS(m1,m2,m3…),而在任何解决方案中,每个成员类型实现特定的方法都是必须的,因此我不认为这是开销。

更新: ENUMERATE_MEMBERS宏有一个非常简单的实现(但是可以稍微扩展以支持从可枚举结构继承)

#define ENUMERATE_MEMBERS(...) \
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) const { EnumerateWithHelper(enumerator, __VA_ARGS__ ); }\
template<typename TEnumerator> inline void EnumerateWith(TEnumerator & enumerator) { EnumerateWithHelper(enumerator, __VA_ARGS__); }

// EnumerateWithHelper
template<typename TEnumerator, typename ...T> inline void EnumerateWithHelper(TEnumerator & enumerator, T &...v) 
{ 
    int x[] = { (EnumerateWith(enumerator, v), 1)... }; 
}

// Generic EnumerateWith
template<typename TEnumerator, typename T>
auto EnumerateWith(TEnumerator & enumerator, T & val) -> std::void_t<decltype(val.EnumerateWith(enumerator))>
{
    val.EnumerateWith(enumerator);
}

这15行代码不需要任何第三方库;)

其他回答

看起来c++仍然没有这个特性。 c++ 11也有延迟反射(

搜索一些宏或者自己制作。Qt还可以帮助进行反射(如果可以使用的话)。

反射本质上是关于编译器决定在运行时代码可以查询的代码中留下哪些足迹。c++以不为不用的东西付费而闻名;因为大多数人不使用/不想要反射,c++编译器通过不记录任何东西来避免成本。

因此,c++不提供反射,并且像其他答案所指出的那样,作为一般规则,自己“模拟”它并不容易。

在“其他技术”下,如果没有带有反射的语言,可以使用一个可以在编译时提取所需信息的工具。

我们的DMS软件再造工具包是通过显式语言定义参数化的通用编译器技术。它有语言定义C, c++, Java, COBOL, PHP,…

对于C、c++、Java和COBOL版本,它提供了对解析树和符号表信息的完整访问。符号表信息包括您可能希望从“反射”中获得的数据类型。如果您的目标是枚举一组字段或方法,并对它们做一些事情,DMS可以用于根据符号表中的内容以任意方式转换代码。

如果你像这样声明一个指向函数的指针:

int (*func)(int a, int b);

您可以像这样在内存中为该函数分配一个位置(需要libdl和dlopen)

#include <dlfcn.h>

int main(void)
{
    void *handle;
    char *func_name = "bla_bla_bla";
    handle = dlopen("foo.so", RTLD_LAZY);
    *(void **)(&func) = dlsym(handle, func_name);
    return func(1,2);
}

要使用间接方式加载局部符号,可以对调用二进制文件(argv[0])使用dlopen。

这样做的唯一要求(除了dlopen()、libdl和dlfcn.h)是知道函数的参数和类型。

我建议使用Qt。

有一个开源许可证和一个商业许可证。

尽管在c++中不支持即时可用的反射,但实现它并不太难。 我遇到了这篇很棒的文章: http://replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

本文详细介绍了如何实现一个非常简单的反射系统。当然,这不是最有益的解决办法,还有一些粗糙的地方有待解决,但对我的需要来说,这已经足够了。

底线——如果做得正确,反射是可以得到回报的,而且在c++中是完全可行的。