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

Nic*_*ick 242 c++ reflection templates sfinae

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

Pau*_* II 236

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

首先,为了使它在预处理器中更容易和更清晰,我们将使用类型化表达式.类型化表达式只是将类型放在括号中的表达式.所以不写作,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
Run Code Online (Sandbox Code Playgroud)

接下来,我们定义一个REFLECTABLE宏来生成关于每个字段的数据(加上字段本身).这个宏将被调用如下:

REFLECTABLE
(
    (const char *) name,
    (int) age
)
Run Code Online (Sandbox Code Playgroud)

因此,使用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)); \
    } \
}; \
Run Code Online (Sandbox Code Playgroud)

这样做是生成一个常量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;
    };
};
Run Code Online (Sandbox Code Playgroud)

现在迭代字段我们使用访问者模式.我们创建一个从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));
}
Run Code Online (Sandbox Code Playgroud)

现在我们把它放在一起.以下是我们如何定义Person可反射的类:

struct Person
{
    Person(const char *name, int age)
        :
        name(name),
        age(age)
    {
    }
private:
    REFLECTABLE
    (
        (const char *) name,
        (int) age
    )
};
Run Code Online (Sandbox Code Playgroud)

这是一个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());
}
Run Code Online (Sandbox Code Playgroud)

使用print_fieldswith reflectable Person类的示例:

int main()
{
    Person p("Tom", 82);
    print_fields(p);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

哪个输出:

name=Tom
age=82
Run Code Online (Sandbox Code Playgroud)

瞧,我们刚刚用100行代码实现了C++中的反射.

  • 感谢他们展示如何实现反思,而不是说它无法完成.像这样的答案使SO成为一个很好的资源. (93认同)
  • 请注意,如果您尝试在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,)` (4认同)
  • 看到我自己的答案-http://stackoverflow.com/a/28399807/2338477我已经提取并重新打包了所有定义,并且不需要boost库。作为演示代码,我提供了对xml的序列化以及从xml恢复的功能。 (2认同)

Joh*_*itb 101

reflection周围有两种游泳方式.

  1. 通过迭代类型的成员,枚举其方法等来进行检查.

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

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

如果您正在寻找方法来完成1),比如查看一个类有多少方法,或者喜欢获取类id的字符串表示,那么我担心没有标准的C++方法.你必须使用其中之一

  • 像Qt元对象编译器这样的元编译器,它可以翻译你的代码,添加额外的元信息.
  • 一个包含宏的框架,允许您添加所需的元信息.你需要告诉框架所有方法,类名,基类和它需要的一切.

C++的设计考虑了速度.如果你想要高级检查,比如C#或Java,那么我恐怕不得不告诉你没有办法没有办法.

  • C++是在考虑速度的基础上制定的,但其理念并非"尽可能快",而是"如果你不使用它就不付钱".我相信一种语言可能以符合这种哲学的方式实现内省,而C++只是缺乏它. (115认同)
  • @jalf:只需要可能需要的元数据.如果我们只考虑编译时反射,这是微不足道的.例如,编译时函数`members <T>`返回T的所有成员的列表.如果我们想要运行时反射(即RTTI与反射混合),编译器仍然会知道所有反射的基类型.很可能``<T>(T&)`永远不会为T = std :: string实例化,因此不需要包含std :: string或其派生类的RTTI. (23认同)
  • 反射库(如下所述)在不降低现有代码速度的情况下为C++添加了反射:http://root.cern.ch/drupal/content/reflex (9认同)
  • @Joseph:应该怎么做?它需要存储所有元数据.这意味着即使您不使用它,也必须付费.(除非你可以将单个类型标记为"支持反射",但是我们几乎可以使用现有的宏观技巧. (8认同)
  • @Joe:反射永远不会减慢现有代码的速度.它只是使交付的东西更大(因为你必须提供类型信息数据库...). (4认同)
  • 一切都有成本.即使您不访问元数据,也需要为其支付运行时内存空间.有些世界没有那么奢侈. (2认同)

Bra*_*son 57

我会喜欢小马,但小马不是免费的.:-P

http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI就是你要得到的.您正在考虑的反思 - 在运行时可用的完全描述性元数据 - 默认情况下C++不存在.

  • 小马的评论!我投票两次,因为你的答案也值得,但遗憾的是我只得到一个,所以小马赢了.:-) (7认同)
  • 我真的不明白为什么这是一个聪明的回应.我已经说过我想引用库等来实现它.反射/内省适用于各种系统,允许脚本访问,序列化等. (5认同)
  • @Nick:他已经回答了.它无法完成,数据不存在,因此,没有库能够为您实现它. (3认同)
  • @jalf 对我来说还是很奇怪,读到编程界的人说“这是不可能的”,而不是“我不知道怎么做”。确保元数据不存在,但可以用宏插入 (3认同)

Kon*_*lph 38

C++不存在RTTI.

这是完全错误的.实际上,术语"RTTI"是由C++标准创造的.另一方面,RTTI在实现反射方面并没有走得太远.


Rod*_*ick 37

信息确实存在 - 但不是以您需要的格式存在,并且仅在您导出类时才存在.这适用于Windows,我不了解其他平台.使用存储类说明符,例如:

class __declspec(export) MyClass
{
public:
    void Foo(float x);
}
Run Code Online (Sandbox Code Playgroud)

这使得编译器将类定义数据构建到DLL/Exe中.但它不是一种可以随意用于反射的格式.

在我的公司,我们构建了一个解释这个元数据的库,并允许您反映一个类,而无需在类本身中插入额外的宏等.它允许按如下方式调用函数:

MyClass *instance_ptr=new MyClass;
GetClass("MyClass")->GetFunction("Foo")->Invoke(instance_ptr,1.331);
Run Code Online (Sandbox Code Playgroud)

这有效地做到了:

instance_ptr->Foo(1.331);
Run Code Online (Sandbox Code Playgroud)

Invoke(this_pointer,...)函数具有可变参数.显然,通过以这种方式调用函数,你可以绕过诸如const-safety之类的东西,因此这些方面被实现为运行时检查.

我确信语法可以改进,它只适用于Win32和Win64到目前为止.我们发现它对于为类提供自动GUI接口,在C++中创建属性,在XML之间进行流式传输等等非常有用,并且不需要从特定的基类派生.如果有足够的需求,也许我们可以将其打造成释放形状.


Kei*_*thB 14

您需要查看您要执行的操作,以及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;
}
Run Code Online (Sandbox Code Playgroud)

第一个调用将此对象添加到过滤系统,该系统调用该BuildMap()方法以确定可用的方法.

然后,在配置文件中,您可以执行以下操作:

FILTER-OUTPUT-OBJECT   MyObject
FILTER-OUTPUT-FILENAME file.txt
FILTER-CLAUSE-1        person == 1773
FILTER-CLAUSE-2        time > 2000
Run Code Online (Sandbox Code Playgroud)

通过一些模板魔术涉及boost,它在运行时被转换为一系列方法调用(当读取配置文件时),因此它非常有效.除非你真的需要,我不建议这样做,但是,当你这样做时,你可以做一些非常酷的事情.


Fer*_*cio 13

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


Jér*_*ôme 13

我建议使用Qt.

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

  • QT,或实施类似方法的其他库是您将获得的最佳选择 (10认同)
  • 在编译时付款或在运行时付款 - 无论你支付哪种方式! (5认同)

phi*_*ant 13

编辑:CAMP不再维护; 有两个叉子可供选择:

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

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

目前的Tegesoft库使用Boost,但也有一个使用C++ 11 的fork,不再需要Boost.


Mic*_*hel 10

我做过类似你曾经做过的事情,虽然可以获得一定程度的反思和访问更高级别的功能,但维护上的头痛可能不值得.我的系统用于通过委托将UI类与业务逻辑完全分离,类似于Objective-C的消息传递和转发概念.这样做的方法是创建一些能够映射符号的基类(我使用字符串池但你可以使用枚举,如果你喜欢速度和编译时错误处理而不是总灵活性)来实现函数指针(实际上不是纯函数指针,但类似于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文档作为蓝图.


Dam*_*xon 10

开箱即用的C++不支持反射.这很令人难过,因为它使防御性测试成为一种痛苦.

有几种做反射的方法:

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

第一个链接看起来最有希望(使用mod's 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


Zac*_*ack 10

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

该接口类似于C#中的反射,它可以在没有任何RTTI的情况下工作.


小智 9

我从C++时代就知道的两个类似反射的解决方案是:

1)如果能够从"对象"基类派生所有类,则使用RTTI,它将为您提供构建类似反射的行为的引导程序.该类可以提供一些方法,如GetMethod,GetBaseClass等.至于这些方法如何工作,您需要手动添加一些宏来装饰您的类型,在幕后创建类型的元数据,以提供GetMethods等的答案.

2)如果您有权访问编译器对象,则另一个选项是使用DIA SDK.如果我没记错的话,这可以让你打开pdbs,它应该包含C++类型的元数据.它可能足以满足您的需求.此页面显示了如何获取类的所有基类型.

这两种解决方案虽然有点难看!没有什么比C++更能让你欣赏C#的奢侈品了.

祝好运.


Ger*_*ago 7

编辑:更新了截至2017年2月7日的断开链接.

我想没人提到这个:

在欧洲核子研究中心,他们使用C++的全反射系统:

CERN Reflex.它似乎工作得很好.


Lui*_*uis 6

我想你可能会发现Dominic Filion的文章"在C++中使用模板反射".它在Game Programming Gems 5的 1.4节中.不幸的是,我没有我的副本,但寻找它,因为我认为它解释了你的要求.


Mat*_* M. 6

这个问题现在有点旧了(不知道为什么我今天仍然会遇到旧问题)但我正在考虑引入编译时反射的BOOST_FUSION_ADAPT_STRUCT.

你可以将它映射到运行时反射当然,它不会太容易,但它可以在这个方向,而它不会反过来:)

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

  • 由minghua(最初编辑该帖子):我挖掘了这个BOOST_FUSION_ADAPT_STRUCT解决方案并最终想出了一个例子.看到这个更新的问题 - [C++使用boost fusion adapt_struct迭代到嵌套的struct字段](http://stackoverflow.com/questions/12084781/c-iterate-into-nested-struct-field-with-boost-fusion-adapt -struct). (2认同)

Ira*_*ter 5

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

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

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

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

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


The*_*Say 5

RareCpp库实现了相当简单和直观的反映 - 所有字段/类型信息都被设计为可以在数组中使用感觉像数组访问。它是为 C++17 编写的,可与 Visual Studios、g++ 和 Clang 配合使用。该库只是头文件,这意味着您只需将“Reflect.h”复制到项目中即可使用它。

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

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

    REFLECT(FuelTank, capacity, currentLevel, tickMarks)
};
Run Code Online (Sandbox Code Playgroud)

这就是全部,不需要额外的代码来设置反射。您可以选择提供类和字段注释,以便能够遍历超类或向字段添加其他编译时信息(例如 Json::Ignore)。

循环字段可以像...一样简单

RareTs::Members<FuelTank>::forEach([&](auto member) {
    std::cout << member.name << std::endl;
});
Run Code Online (Sandbox Code Playgroud)

您可以包含一个实例/获取值和访问类型信息:

RareTs::Members<FuelTank>::forEach(fuelTank, [](auto member, auto & value) {
    using MemberType = typename decltype(member)::type;
    std::cout << RareTs::toStr<MemberType>() << " " << member.name
        << ": " << value << std::endl;
});
Run Code Online (Sandbox Code Playgroud)

JSON构建在 RareCpp 之上,它自动识别适当的 JSON 输出表示形式以供读取或写入,并且可以递归遍历任何反射字段以及数组和 STL 容器。

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

    REFLECT(MyObject, myInt, myString, 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);
}
Run Code Online (Sandbox Code Playgroud)

上面的代码可以像这样运行......

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 ]
}
Run Code Online (Sandbox Code Playgroud)

也可以看看...


Nic*_*ick 4

Ponder是一个C++反射库,回答了这个问题。我考虑了这些选择并决定自己制作,因为我找不到一个符合我所有要求的选项。

尽管这个问题有很好的答案,但我不想使用大量的宏,也不想依赖 Boost。Boost 是一个很棒的库,但是有许多小型定制的 C++0x 项目更简单并且编译时间更快。能够从外部修饰类也有一些优点,例如包装还不支持 C++11 的 C++ 库。它是 CAMP 的分支,使用 C++11,不再需要 Boost