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


当前回答

我想宣传一下自动自省/反射工具包“IDK”的存在。它使用类似Qt的元编译器,并将元信息直接添加到目标文件中。据说它很容易使用。没有外部依赖。它甚至允许您自动反映std::string,然后在脚本中使用它。请看IDK

其他回答

在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行代码不需要任何第三方库;)

您需要查看您正在尝试做什么,以及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的模板魔法,可以在运行时(读取配置文件时)将其转换为一系列方法调用,因此相当高效。我不建议你这样做,除非你真的需要,但是,当你这样做的时候,你可以做一些非常酷的事情。

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

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

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

当我想要在c++中进行反射时,我读了这篇文章并改进了我在那里看到的东西。对不起,没有罐头。结果不是我的…但你当然可以得到我所拥有的,然后从那里开始。

我目前正在研究,当我喜欢的时候,使用inherit_linear的方法使可反射类型的定义更容易。实际上我已经学了很多,但还有一段路要走。c++ 0x中的变化很可能在这方面有很大的帮助。

您需要做的是让预处理器生成关于字段的反射数据。该数据可以存储为嵌套类。

首先,为了在预处理器中更容易更清晰地编写它,我们将使用类型化表达式。类型化表达式只是将类型放在括号中的表达式。所以不是写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。它也与反射器类为友,这是为了它可以访问字段,即使它们是私有的:

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;
    };
};

现在要遍历字段,我们使用访问者模式。我们创建一个MPL范围,从0到字段的数量,并访问该索引下的字段数据。然后它将字段数据传递给用户提供的访问者:

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

瞧,我们刚刚用c++实现了反射,用了不到100行代码。