如何向C ++应用程序添加反射?


263

我希望能够对C ++类的名称,内容(即成员及其类型)等进行自省。我在这里说的是本机C ++,而不是具有反射的托管C ++。我意识到C ++使用RTTI提供了一些有限的信息。哪些其他库(或其他技术)可以提供此信息?


18
不幸的是,如果没有宏和其他预处理功能,就无法做到这一点,因为除非通过某些宏预处理魔术手动创建它,否则所需的元数据将不存在
jalf

6
您可以从RTTI那里获得的信息不足以完成您实际上想要反映的大多数事情。例如,您不能遍历类的成员函数。
约瑟夫·加文

Answers:


260

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

首先,为了更轻松,更干净地在预处理器中编写它,我们将使用类型化表达式。带类型的表达式只是将类型放在括号中的表达式。因此,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;
    };
};

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

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

print_fields在可反射Person类中使用的示例:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}

哪个输出:

name=Tom
age=82

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


106
展示如何实现反射而不是说不能完成反射的荣誉。这样的答案使SO成为了很好的资源。
fearless_fool 2014年

4
请注意,如果您尝试在Visual Studio下进行编译,则会收到错误消息,因为VS无法正确处理可变参数宏扩展。对于VS,请尝试添加: #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 2014年

我收到错误消息“ BOOST_PP_IIF_0”,但未命名类型。你能帮忙吗?
Ankit Zalani 2015年

3
看到我自己的答案-stackoverflow.com/a/28399807/2338477我已经提取并重新打包了所有定义,并且不需要boost库。作为演示代码,我提供了对xml的序列化以及从xml恢复的功能。
TarmoPikaro

107

reflection周围有两种游泳。

  1. 通过遍历类型的成员,枚举其方法等进行检查。

    对于C ++,这是不可能的。
  2. 通过检查类类型(类,结构,联合)是否具有方法或嵌套类型来进行检查,该方法是从另一个特定类型派生的。

    使用C ++可以实现这种事情template-tricks。使用boost::type_traits了很多东西(如检查类型是否为整数)。要检查成员函数的存在,请使用是否可以编写模板来检查函数的存在?。要检查是否存在某种嵌套类型,请使用普通SFINAE

如果您宁愿寻找完成1)的方法,例如查看一个类有多少种方法,或者喜欢获取一个类id的字符串表示形式,那么恐怕没有标准的C ++方法可以做到这一点。您必须使用

  • 像Qt Meta Object Compiler这样的Meta编译器,可以翻译代码并添加其他元信息。
  • 由宏组成的框架,允许您添加所需的元信息。您将需要告诉框架所有方法,类名,基类及其所需的一切。

C ++的设计考虑了速度。如果您想要像C#或Java这样的高级检查,那么恐怕我不得不告诉您,没有任何努力就没有办法。


122
C ++的设计考虑到了速度,但是其哲学并不是“尽可能快”,而是“如果不使用它,您就不会为此付出代价”。我相信,一种语言可能会以适合该哲学的方式来实现内省,而C ++却缺少它。
约瑟夫·加文

8
@约瑟夫:那应该怎么做?它需要存储所有的元数据。这意味着即使您不使用它也必须付费。(除非你可以将个别类型为“支持反省”,但后来我们几乎下来,我们还不如用现有的宏观弄虚作假。
jalf

25
@jalf:仅可能需要的元数据。如果仅考虑编译时反射,这是微不足道的。例如,一个编译时函数members<T>返回T的所有成员的列表。如果我们想要运行时反射(即RTTI与反射混合),则编译器仍将知道所有反射的基本类型。很可能members<T>(T&)永远不会为T = std :: string实例化它,因此不需要包括std :: string的RTTI或其派生类。
MSalters,2009年

9
反射库(如下所述)在不降低现有代码速度的情况下向C ++添加了反射:root.cern.ch/drupal/content/reflex
Joseph Lisee 2011年

6
@Joe:反射永远不会降低现有代码的速度。它只会使交付的内容变大(因为您必须交付类型信息数据库...)。
mmmmmmmm 2012年

57

我会爱小马,但小马不是免费的。:-p

您将获得http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI。就像您正在思考的那样,反射-运行时可以使用完全描述性的元数据-默认情况下,C ++不存在。


1
我第二个布拉德。C ++模板可能非常强大,并且围绕各种“反射”类型行为(例如boost'any'库,类型特征,C ++ RTTI等)具有丰富的经验,可以解决反射所解决的许多问题。尼克,您的目标是什么?
亚伦,

7
支持小马评论!我会投票两次,因为您的回答也应得,但可悲的是我只得到一个,因此小马获胜。:-)
弗朗西·佩诺夫(Francis Penov)

6
我真的不明白为什么这是一个聪明的回应。我已经说过,我想引用库等来实现此目的。反射/反省是各种系统,允许脚本访问,系列化等
尼克

3
@尼克:他已经回答了。它无法完成,数据不存在,因此没有库能够为您实现它。
jalf

@jalf对我来说仍然很奇怪,读过编程世界的人说的想法是“不可能”而不是“我不知道如何”。确定元数据不存在,但可以插入宏
Freddx L.

39

信息确实存在-但不以您需要的格式提供,并且仅在您导出类时才提供。这在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。我们发现,对于类具有自动GUI界面,在C ++中创建属性,与XML进行流传输以及从XML进行流传输等确实非常有用,并且不需要从特定的基类派生。如果有足够的需求,也许我们可以将其成型以供发布。


1
我想是您的意思__declspec(dllexport),如果您在构建期间启用了创建此类信息的权限,则可以从.map文件中检索信息。
Orwellophile

17

开箱即用的C ++不支持反射。这很可悲,因为它使防御测试变得痛苦。

有几种方法可以进行反射:

  1. 使用调试信息(非便携式)。
  2. 在您的代码中添加宏/模板或其他一些源代码方法(看起来很丑)
  3. 修改诸如clang / gcc之类的编译器以生成数据库。
  4. 使用Qt MOC方法
  5. 加强反思
  6. 精确而平坦的反射

第一个链接看起来是最有前途的(使用mod代替clang),第二个链接讨论了多种技术,第三个是使用gcc的不同方法:

  1. http://www.donw.org/rfl/

  2. https://bitbucket.org/dwilliamson/clreflect

  3. https://root.cern.ch/how/how-use-reflex

现在有一个C ++反射工作组。查看C ++ 14 @ CERN的新闻:

编辑13/08/17:

自从最初的职位以来,在反思方面已有许多潜在的进步。以下内容提供了更多详细信息,并讨论了各种技术和状态:

  1. 简而言之,静态反射
  2. 静态反射
  3. 静态反射设计

但是,除非在社区中有更多的兴趣支持C ++中的反射,否则在不久的将来使用C ++中的标准化反射方法看起来并不乐观。

下面根据上次C ++标准会议的反馈详细介绍当前状态:

编辑13/12/2017

反思似乎正在朝着C ++ 20或更可能是TSR迈进。但是运动缓慢。

编辑15/09/2018

TS草案已发送给国家机构进行投票。

文本可以在这里找到:https : //github.com/cplusplus/reflection-ts

编辑11/07/2019

Reflection TS功能齐全,将在整个夏季(2019)进行评论和投票。

元模板编程方法将被更简单的编译时代码方法(未在TS中反映)取代。

编辑10/02/2020

这里有一个在Visual Studio中支持反射TS的请求:

关于作者David Sankel的TS的谈话:

编辑2020年3月17日

反思工作正在取得进展。可以在这里找到“ 2020-02布拉格ISO C ++委员会旅行报告”的报告:

有关C ++ 23考虑的内容的详细信息,可以在此处找到(包括关于Reflection的简短内容):

编辑2020年6月4日

Jeff Preshing发布了一个名为“ Plywood”的新框架,其中包含一个运行时反射机制。更多详情可在这找到:

到目前为止,工具和方法看起来是最完善和最容易使用的。


1
CERN链接已损坏。
Mostowski崩溃2015年

cern链接现在应该已修复。它们倾向于经常断裂,这是很痛苦的。
达米安·迪克森

这个答案是否仅涉及编译时反射?
einpoklum

@einpoklum当前唯一的反射解决方案是编译时,通常使用元模板代码或宏。最新的TS草案似乎可以在运行时使用,但您必须使用正确的编译器构建所有库,以存储所需的元数据。
达米安·迪克森

@DamianDixon:事实并非如此。有几个运行时反射库。现在,理所当然的是,它们相当笨拙,要么选择加入,要么需要编译器提名,但它们仍然存在。根据我的理解,如果您仅提到编译时反射,请编辑您的答案以使其更清晰。
einpoklum

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,它可以在运行时(读取配置文件时)转换为一系列方法调用,因此相当高效。除非您确实需要,否则我不建议您这样做,但是当您这样做时,您可以做一些非常酷的事情。


必须喜欢这些始终返回true的函数;)我认为这不受静态init排序问题的影响?
保罗

14

我建议使用Qt

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


1
我看了看,但是它使用宏,并且源代码需要解析才能生成元数据代码。我想避免这一额外步骤。我更喜欢使用C ++库或简单的宏。谢谢你的主意。
尼克,

10
QT,或实施了类似的做法另一个库是最好的,你会得到
jalf

5
在编译时付款或在运行时付款-两种付款方式!
马丁·贝克特

13

您想用反射做什么?
您可以将Boost 类型特征typeof库用作编译时反射的有限形式。也就是说,您可以检查和修改传递给模板的类型的基本属性。


13

编辑不再维护; 提供两个叉子:

  • 一个也称为CAMP,并且基于相同的API。
  • Ponder是部分重写,应首选使用,因为它不需要Boost;它使用的是C ++ 11。

CAMP是MIT许可的库(以前为LGPL),可为C ++语言增加反射。在编译中不需要特定的预处理步骤,但是绑定必须手动进行。

当前的Tegesoft库使用Boost,但是还有一个使用C ++ 11 的fork 不再需要Boost


11

我曾经做过类似您想做的事情,虽然有可能获得一定程度的反思和访问更高级别的功能,但是维护麻烦可能不值得。我的系统用于通过类似于Objective-C的消息传递和转发概念的委派来使UI类与业务逻辑完全分开。做到这一点的方法是创建一些能够将符号映射到函数指针的基类(我使用了一个字符串池,但是如果您更喜欢速度和编译时错误处理而不是总的灵活性,则可以使用枚举来实现)到函数指针(实际上不是纯函数指针,但类似于Boost在Boost.Function中的功能(我当时无法访问)。只要您有一些可以表示任何值的通用基类,就可以对成员变量执行相同的操作。整个系统毫不掩饰地窃取了键值编码和委派,其副作用可能值得花大量的时间来使使用该系统的每个类将其所有方法和成员与合法电话进行匹配:1)任何类都可以在任何其他类上调用任何方法,而不必包含标头或编写伪造的基类,因此可以为编译器预定义接口;和2)成员变量的getter和setter易于使用线程安全,因为更改或访问它们的值始终是通过所有对象的基类中的2个方法完成的。整个系统毫不掩饰地窃取了键值编码和委派,其副作用可能值得花大量的时间来使使用该系统的每个类将其所有方法和成员与合法电话进行匹配:1)任何类都可以在任何其他类上调用任何方法,而不必包含标头或编写伪造的基类,因此可以为编译器预定义接口;和2)成员变量的getter和setter易于使用线程安全,因为更改或访问它们的值始终是通过所有对象的基类中的2个方法完成的。整个系统毫不掩饰地窃取了键值编码和委派,其副作用可能值得花大量的时间来使使用该系统的每个类将其所有方法和成员与合法电话进行匹配:1)任何类都可以在任何其他类上调用任何方法,而不必包含标头或编写伪造的基类,因此可以为编译器预定义接口;和2)成员变量的getter和setter易于使用线程安全,因为更改或访问它们的值始终是通过所有对象的基类中的2个方法完成的。1)任何类都可以在任何其他类上调用任何方法,而不必包含标头或编写伪造的基类,因此可以为编译器预定义接口;和2)成员变量的getter和setter易于使用线程安全,因为更改或访问它们的值始终是通过所有对象的基类中的2个方法完成的。1)任何类都可以在任何其他类上调用任何方法,而不必包含标题或编写伪造的基类,因此可以为编译器预定义接口;和2)成员变量的getter和setter易于使用线程安全,因为更改或访问它们的值始终是通过所有对象的基类中的2个方法完成的。

这也导致可能做一些真正奇怪的事情,而这些事情在C ++中是不容易的。例如,我可以创建一个包含任何类型的任意项(包括其自身)的Array对象,并通过将消息传递给所有数组项并收集返回值来动态创建新数组(类似于Lisp中的map)。另一个是键值观察的实现,通过该操作,我可以将UI设置为立即响应后端类的成员的更改,而不必不断轮询数据或不必要地重绘显示。

对您来说也许更有趣的是,您还可以转储为类定义的所有方法和成员,并且转储形式也不少于字符串。

该系统的缺点可能会打扰您:添加所有消息和键值非常繁琐;比没有任何思考要慢;你会成长讨厌看到boost::static_pointer_castboost::dynamic_pointer_cast与各地暴力激情你的代码; 强类型系统的局限性仍然存在,您实际上只是将它们隐藏了一点,因此并不那么明显。字符串中的错别字也不是有趣或容易发现的惊喜。

至于如何实现这样的事情:只需要使用共享和弱指针来指向一些通用的基数(我的想象力很强地称为“对象”),并为所有要使用的类型派生。我建议安装Boost.Function而不是像以前那样安装,它带有一些自定义废话和大量难看的宏来包装函数指针调用。由于所有内容均已映射,因此检查对象仅是遍历所有键的问题。由于我的课程从本质上讲仅使用C ++尽可能接近直接获取Cocoa,所以如果您想要类似的东西,那么我建议您使用Cocoa文档作为蓝图。


嘿,@ Michael;您是否仍然有此源代码,或者您已经摆脱了它?如果您不介意的话,我想看看。
RandomDSdevel 2014年

糟糕,您的名字拼写错误!没有难怪我从来没有得到答复...
RandomDSdevel

10

在C ++中还有另一个新的反射库,称为RTTR(运行时类型反射,另请参见github)。

该接口类似于C#中的反射,并且无需任何RTTI即可使用。


8

从我的C ++时代开始,我知道两种类似于反射的解决方案:

1)使用RTTI,如果您能够使所有类都从“对象”基类派生,则可以使用它来建立类似反射的行为。该类可以提供一些方法,例如GetMethod,GetBaseClass等。关于这些方法的工作方式,您将需要手动添加一些宏来装饰您的类型,这些宏在幕后在该类型中创建元数据以提供对GetMethods等的答案。

2)如果您有权访问编译器对象,则另一个选择是使用DIA SDK。如果我没记错的话,这可以让您打开pdbs,其中应包含C ++类型的元数据。做您需要的事情可能就足够了。例如,此页面显示如何获取类的所有基本类型。

这些解决方案都有点丑陋!没有什么比C ++能让您欣赏C#的奢侈了。

祝好运。


这是狡猾的,是一个巨大的黑客,您在此处建议使用DIA SDK。
Sqeaky 2012年

7

编辑:自2017年2月7日起更新了断开的链接。

我认为没有人提到:

在CERN,他们将完整的反射系统用于C ++:

CERN反射。看来效果很好。


@ j4nbur53链接被打破,因为他们似乎达到了一个里程碑:root.cern.ch
赫尔曼DIAGO

可能是您的意思是此链接root.cern.ch/root/doc/ROOTUsersGuideHTML/ch07.html Chapter Reflex吗?
莫斯托夫斯基崩溃

试试这个root.cern.ch/how/how-use-reflex。Reflex充当生成器来解析您的头文件并生成C ++自省代码/库,您可以链接并使用简单的api。
亚当·里奇科夫斯基

6

这个问题现在有点老了(不知道为什么今天我会继续提出老问题),但是我正在考虑引入编译时反射的BOOST_FUSION_ADAPT_STRUCT

当然,这取决于您将其映射到运行时反射,这并不是一件容易的事,但是有可能朝这个方向发展,而并非相反:)

我真的认为封装一个宏BOOST_FUSION_ADAPT_STRUCT可以生成必要的方法来获取运行时行为。


2
由minghua(最初编辑帖子)撰写:我研究了这个BOOST_FUSION_ADAPT_STRUCT解决方案,并最终提出了一个示例。请参阅此更新的SO问题-C ++通过boost Fusion自适应适配器迭代到嵌套的struct字段中
Matthieu M. 2012年

太好了,马修!刚刚意识到在过去的一年中到处都有您的提示。直到现在都没有发现它们之间的关系。这些非常鼓舞人心。
minghua 2012年

6

我认为您可能会发现Dominic Filion的文章“在C ++中使用反射模板”很有趣。它在《Game Programming Gems 5》的 1.4节中。不幸的是,我没有随身携带的复印件,但要寻找它,因为我认为它可以解释您的要求。


4

思考是一个C ++反射库,可以回答这个问题。我考虑了各种选择,并决定自己做,因为我找不到能打勾所有选择的选项。

尽管这个问题有很好的答案,但我不想使用大量的宏,也不希望使用Boost。Boost是一个很棒的库,但是有许多小的定制C ++ 0x项目,它们更简单并且编译速度更快。能够在外部装饰类还有其他好处,例如包装不支持C ++ 11的C ++库。它是使用C ++ 11的CAMP的分支,不再需要Boost


4

反思本质上是关于编译器决定在运行时代码可以查询的代码中留下的内容。C ++以不用付您不使用的东西而闻名。因为大多数人不使用/不希望反射,所以C ++编译器通过不记录任何内容来避免成本。

因此,C ++不能提供反射,而且要像其他答案所指出的那样,一般性地自己“模拟”它并不容易。

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

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

对于C,C ++,Java和COBOL版本,它提供对语法分析树和符号表信息的完整访问。该符号表信息包括“反射”可能需要的数据类型。如果您的目标是枚举一组字段或方法,并且一些与他们,DMS可用于根据您在以任意方式的符号表中查找转换的代码。


3

您可以在此处找到另一个库:http : //www.garret.ru/cppreflection/docs/reflect.html 它支持2种方法:从调试信息中获取类型信息,并让程序员提供此信息。

我也对我的项目的反射感兴趣,并找到了这个库,我还没有尝试过,但是尝试了这个家伙的其他工具,我喜欢它们的工作方式:-)


3

查看Classdesc http://classdesc.sf.net。它以类“描述符”的形式提供反射,可与任何标准C ++编译器一起使用(是的,它可以与Visual Studio以及GCC一起使用),并且不需要源代码注释(尽管存在一些杂物来处理棘手的情况) )。它已经开发了十多年,并用于许多工业规模的项目。


1
欢迎使用堆栈溢出。尽管这个答案是关于主题的,但必须指出您是该软件的作者,以明确表明它不是一个公正的建议:-)
Matthew Strawbridge

2

当我想用C ++进行反思时,我读了这篇文章,并对那里的内容进行了改进。对不起,没有可以。我不拥有结果...但是您当然可以得到我拥有的东西并从那里去。

我目前正在研究自己喜欢的方法,以线性方式使用Inherited_linear来简化可反射类型的定义。实际上我已经走了很远,但是我还有路要走。C ++ 0x的更改很有可能在此领域提供很多帮助。


2

看来C ++仍然没有此功能。而且C ++ 11也推迟了反思((

搜索一些宏或创建自己的宏。Qt也可以帮助反射(如果可以使用的话)。


2

即使在C ++中不立即支持反射,也很难实施。我遇到了这篇很棒的文章:http : //replicaisland.blogspot.co.il/2010/11/building-reflective-object-system-in-c.html

本文详细解释了如何实现一个非常简单且基本的反射系统。虽然它不是最有益的解决方案,但仍有一些粗糙的地方可以解决,但对于我的需求而言,这已经足够了。

最重要的是-如果正确完成,反射可以取得回报,并且在c ++中完全可行。


2

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


2

如果您正在寻找相对简单的C ++反射-我从各种来源收集了宏/定义,并对其进行了注释。您可以从此处下载头文件:

https://github.com/tapika/TestCppReflect/blob/master/MacroHelpers.h

一组定义以及最重要的功能:

https://github.com/tapika/TestCppReflect/blob/master/CppReflect.h https://github.com/tapika/TestCppReflect/blob/master/CppReflect.cpp https://github.com/tapika/TestCppReflect/ blob / master / TypeTraits.h

示例应用程序也位于git仓库中,位于以下位置:https : //github.com/tapika/TestCppReflect/

我将在此处部分复制并进行解释:

#include "CppReflect.h"
using namespace std;


class Person
{
public:

    // Repack your code into REFLECTABLE macro, in (<C++ Type>) <Field name>
    // form , like this:

    REFLECTABLE( Person,
        (CString)   name,
        (int)       age,
...
    )
};

void main(void)
{
    Person p;
    p.name = L"Roger";
    p.age = 37;
...

    // And here you can convert your class contents into xml form:

    CStringW xml = ToXML( &p );
    CStringW errors;

    People ppl2;

    // And here you convert from xml back to class:

    FromXml( &ppl2, xml, errors );
    CStringA xml2 = ToXML( &ppl2 );
    printf( xml2 );

}

REFLECTABLEdefine使用带有offsetof-的类名+字段名来标识特定字段位于内存中的哪个位置。我尝试了尽可能多的.NET术语,但是C ++和C#是不同的,因此不是1对1。整个C ++反射模型都驻留在TypeInfoand FieldInfo类中。

我已经使用pugi xml解析器将演示代码提取到xml中,并将其从xml中恢复回来。

因此,演示代码产生的输出如下所示:

<?xml version="1.0" encoding="utf-8"?>
<People groupName="Group1">
    <people>
        <Person name="Roger" age="37" />
        <Person name="Alice" age="27" />
        <Person name="Cindy" age="17" />
    </people>
</People>

也可以通过TypeTraits类和部分模板规范启用任何第3方类/结构支持-以类似于CString或int的方式定义自己的TypeTraitsT类-请参见中的示例代码

https://github.com/tapika/TestCppReflect/blob/master/TypeTraits.h#L195

该解决方案适用于Windows / Visual Studio。可以将其移植到其他OS /编译器,但还没有完成。(如果您真的喜欢解决方案,请问我,我可能会为您提供帮助)

此解决方案适用于具有多个子类的一类的一枪序列化。

但是,如果您正在寻找序列化类部分甚至控制反射调用产生的功能的机制,则可以查看以下解决方案:

https://github.com/tapika/cppscriptcore/tree/master/SolutionProjectModel

可以从youtube视频中找到更多详细信息:

C ++运行时类型反射 https://youtu.be/TN8tJijkeFE

我试图更深入地解释c ++反射将如何工作。

示例代码如下所示:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/testCppApp.cpp

c.General.IntDir = LR"(obj\$(ProjectName)_$(Configuration)_$(Platform)\)";
c.General.OutDir = LR"(bin\$(Configuration)_$(Platform)\)";
c.General.UseDebugLibraries = true;
c.General.LinkIncremental = true;
c.CCpp.Optimization = optimization_Disabled;
c.Linker.System.SubSystem = subsystem_Console;
c.Linker.Debugging.GenerateDebugInformation = debuginfo_true;

但是实际上,这里的每个步骤都会导致函数调用将C ++属性与一起使用__declspec(property(get =, put ... )

它以路径的形式接收有关C ++数据类型,C ++属性名称和类实例指针的完整信息,并基于该信息,您可以生成xml,json,甚至可以通过Internet序列化该信息。

此类虚拟回调函数的示例可在此处找到:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/VCConfiguration.cpp

请参见函数ReflectCopy和虚函数::OnAfterSetProperty

但由于主题确实很先进-我建议您先检查视频。

如果您有任何改进的想法,请随时与我联系。


1

在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());
}

而且我们在“秘密宏”附近有通用功能;)

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宏,您可以构建序列化,比较,哈希和其他内容,而无需触及原始类型,唯一的要求是为每个类型(不能枚举)的每个枚举器实现“ EnumerateWith”方法(例如BinaryWriter) 。通常,您将必须实现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行代码不需要任何第三方库;)


1

您可以使用Boost :: Hana库中的BOOST_HANA_DEFINE_STRUCT为结构实现酷的静态反射功能。
Hana用途广泛,不仅适用于您想到的用例,而且适用于许多模板元编程。


1

随机访问的反思库使得对相当容易和直观的反映-所有现场/类型信息被设计用来在阵列中采用或感觉像数组访问。它是为C ++ 17编写的,可与Visual Studios,g ++和Clang一起使用。该库仅是标头,这意味着您只需将“ Reflect.h”复制到项目中即可使用。

反映的结构或类需要REFLECT宏,您可以在其中提供要反映的类的名称和字段的名称。

class FuelTank {
    public:
        float capacity;
        float currentLevel;
        float tickMarks[2];

    REFLECT(() FuelTank, () capacity, () currentLevel, () tickMarks)
};

这就是全部,无需其他代码即可设置反射。(可选)您可以提供超类(在第一个参数的括号中)和字段注释(在您要注释的字段之前的括号中),以便能够遍历超类或向字段中添加其他编译时信息(例如Json: :忽视)。

遍历字段可以很简单...

for ( size_t i=0; i<FuelTank::Class::TotalFields; i++ )
    std::cout << FuelTank::Class::Fields[i].name << std::endl;

您可以遍历对象实例以访问字段值(可以读取或修改)和字段类型信息...

FuelTank::Class::ForEachField(fuelTank, [&](auto & field, auto & value) {
    using Type = typename std::remove_reference<decltype(value)>::type;
    std::cout << TypeToStr<Type>() << " " << field.name << ": " << value << std::endl;
});

JSON库建立在RandomAccessReflection的顶部,其自动识别用于读取或写入,并且可以递归遍历任何反射字段,以及阵列和STL容器适当JSON输出表示。

struct MyOtherObject { int myOtherInt; REFLECT(() MyOtherObject, () myOtherInt) };
struct MyObject
{
    int myInt;
    std::string myString;
    MyOtherObject myOtherObject;
    std::vector<int> myIntCollection;

    REFLECT(() MyObject, () myInt, () myString, (Reflected) myOtherObject, () myIntCollection)
};

int main()
{
    MyObject myObject = {};
    std::cout << "Enter MyObject:" << std::endl;
    std::cin >> Json::in(myObject);
    std::cout << std::endl << std::endl << "You entered:" << std::endl;
    std::cout << Json::pretty(myObject);
}

以上可以这样运行...

Enter MyObject:
{
  "myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
  "myOtherObject": {
    "myOtherInt": 9001
  }
}


You entered:
{
  "myInt": 1337,
  "myString": "stringy",
  "myOtherObject": {
    "myOtherInt": 9001
  },
  "myIntCollection": [ 2, 4, 6 ]
}

也可以看看...


0

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

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

您可以像这样向函数分配内存中的位置(要求libdldlopen

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

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

此唯一的要求(除了其他dlopen()libdldlfcn.h被知道参数和函数的类型)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.