【问题标题】:How can I add reflection to a C++ application?如何向 C++ 应用程序添加反射?
【发布时间】:2010-09-07 16:10:18
【问题描述】:

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

【问题讨论】:

  • 运气不好,如果没有宏和其他预处理,您将无法做到这一点,因为所需的元数据不存在,除非您通过一些宏预处理魔法手动创建它。跨度>
  • 您可以从 RTTI 获得的信息不足以完成您实际上需要反思的大多数事情。例如,您不能迭代类的成员函数。

标签: c++ reflection templates sfinae


【解决方案1】:

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

首先,为了在预处理器中更容易和更清晰地编写它,我们将使用类型化表达式。类型化表达式只是将类型放在括号中的表达式。所以不要写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

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

【讨论】:

  • 感谢展示如何实现反射,而不是说它不能完成。正是这样的答案让 S.O.很好的资源。
  • 请注意,如果你尝试在 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,)
  • 我收到错误“BOOST_PP_IIF_0”没有命名类型。你能帮忙吗?
  • 查看我自己的答案 - stackoverflow.com/a/55364085/2338477 我已经提取并重新打包了所有定义,并且不需要 boost 库。作为演示代码,我提供对 xml 的序列化并从 xml 恢复。 (感谢@stackprotector 的更正)
  • 非常有帮助。谢谢
【解决方案2】:

有两种reflection游来游去。

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

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

    使用 template-tricks 的 C++ 可以实现这种情况。在很多事情上使用boost::type_traits(比如检查一个类型是否是整数)。要检查成员函数的存在,请使用 Is it possible to write a template to check for a function's existence? 。要检查某个嵌套类型是否存在,请使用普通的 SFINAE

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

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

C++ 的设计考虑了速度。如果你想像 C# 或 Java 那样进行高级检查,那么恐怕我不得不告诉你,不费吹灰之力是没有办法的。

【讨论】:

  • C++ 的设计考虑了速度,但其理念不是“尽可能快”,而是“如果你不使用它,你就不会为此付费”。我相信一种语言有可能以符合该哲学的方式实现内省,而 C++ 只是缺乏它。
  • @Joseph:应该怎么做?它需要存储所有元数据。这意味着您必须为此付费,即使您不使用它。 (除非您可以将单个类型标记为“支持反射”,但我们几乎无法使用现有的宏技巧。
  • @jalf:只有可能需要的元数据。如果我们只考虑编译时反射,这是微不足道的。例如。一个编译时函数members&lt;T&gt;,它返回 T 的所有成员的列表。如果我们想要运行时反射(即 RTTI 与反射混合),编译器仍然会知道所有反射的基本类型。 members&lt;T&gt;(T&amp;) 很可能永远不会为 T=std::string 实例化,因此不需要包含 std::string 或其派生类的 RTTI。
  • 反射库(如下所述)在不减慢现有代码的情况下向 C++ 添加反射:root.cern.ch/drupal/content/reflex
  • @Joe:反射永远不会减慢现有代码的速度。它只是使交付的东西更大(因为您必须交付类型信息数据库......)。
【解决方案3】:

我会喜欢一匹小马,但小马不是免费的。 :-p

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

【讨论】:

  • 我第二个布拉德。 C++ 模板可以相当强大,并且在各种“反射”类型行为方面有丰富的经验,例如 boost 'any' 库、类型特征、C++ RTTI 等,可以解决反射解决的许多问题。那么尼克,你在这里的目标是什么?
  • 为小马的言论点赞!我会投票两次,因为你的回答也值得,但遗憾的是我只得到了一次,所以小马赢了。 :-)
  • 我真的不明白为什么这是一个聪明的回应。我已经说过我希望引用库等来实现这一点。反射/自省是为了各种系统允许脚本访问、序列化等。
  • @Nick:他已经回答过了。做不到,数据不存在,因此没有库可以为你实现。
  • @jalf 读到编程界的人说“这不可能”而不是“我不知道怎么做”对我来说仍然很奇怪。确定元数据不存在,但可以使用宏插入
【解决方案4】:

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

进行反射有几种方法:

  1. 使用调试信息(不可移植)。
  2. 在代码中添加宏/模板或其他一些源方法(看起来很难看)
  3. 修改编译器(如 clang/gcc)以生成数据库。
  4. 使用 Qt moc 方法
  5. Boost Reflect
  6. Precise and Flat Reflection

第一个链接看起来最有前途(使用 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. Static Reflection in a Nutshell
  2. Static Reflection
  3. A design for static reflection

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

以下根据上次 C++ 标准会议的反馈详细说明当前状态:

2017 年 13 月 12 日编辑

Reflection 看起来正在向 C++ 20 或更可能成为 TSR 发展。但是移动很慢。

2018 年 9 月 15 日编辑

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

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

2019 年 11 月 7 日编辑

反射 TS 功能完整,并在夏季(2019 年)发布评论和投票。

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

2020 年 10 月 2 日编辑

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

作者 David Sankel 谈 TS:

2020 年 3 月 17 日编辑

在反思方面正在取得进展。可在此处找到“2020-02 布拉格 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 https://www.youtube.com/watch?v=60ECEc-URP8 上的 ACCU 在线演讲也非常值得一听,它讨论了当前对标准的提议以及基于 clang 的实现。

见:

【讨论】:

  • cern 链接已断开。
  • cern 链接现在应该已修复。它们往往会经常断裂,这很痛苦。
  • 这个答案是否只考虑编译时反射?
  • @einpoklum 目前唯一的反射解决方案是编译时间,通常使用元模板代码或宏。最新的 TS 草案看起来应该适用于运行时,但您必须使用正确的编译器构建所有库才能存储必要的元数据。
  • @DamianDixon:这不是真的。有几个运行时反射库。现在,当然,它们相当笨重,要么选择加入,要么需要编译器更新,但它们仍然存在。如果我理解您的评论,您只提到编译时反射,请编辑您的答案以使其更清晰。
【解决方案5】:

该信息确实存在 - 但不是您需要的格式,并且只有在您导出您的课程时。这适用于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-safety 之类的东西,所以这些方面都是作为运行时检查来实现的。

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

【讨论】:

  • 我认为您的意思是__declspec(dllexport),如果您在构建期间启用创建此类文件,则可以从 .map 文件中检索信息。
【解决方案6】:

您需要查看您正在尝试做什么,以及 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 的函数;)我认为这不会受到静态初始化排序问题的影响?
【解决方案7】:

我建议使用Qt

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

【讨论】:

  • 我看过这个,但它使用宏并且需要解析源代码来生成元数据代码。我想避免这个额外的步骤。我更喜欢使用 C++ 库或简单的宏。不过感谢您的想法。
  • QT 或其他实现类似方法的库是您将获得的最佳选择
  • 在编译时支付或在运行时支付 - 无论哪种方式支付!
【解决方案8】:

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

【讨论】:

    【解决方案9】:

    编辑CAMP 不再维护;有两个叉子可用:

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

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

    当前的 Tegesoft 库使用 Boost,但也有使用 C++11 的 a fork不再需要 Boost

    【讨论】:

      【解决方案10】:

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

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

      您可能更感兴趣的是,您还可以转储为类定义的所有方法和成员,并且以字符串形式转储。

      系统的缺点可能会阻止您打扰:添加所有消息和键值非常繁琐;它比没有任何反射要慢;你会越来越讨厌看到 boost::static_pointer_castboost::dynamic_pointer_cast 在你的代码库中充满激情;强类型系统的局限性仍然存在,您实际上只是将它们隐藏了一点,所以它并不那么明显。字符串中的错别字也不是一个有趣或容易发现的惊喜。

      至于如何实现这样的事情:只需使用指向某些公共基的共享和弱指针(我的非常富有想象力地称为“对象”)并为您想要使用的所有类型派生。我建议安装 Boost.Function 而不是按照我的方式安装,即使用一些自定义废话和大量丑陋的宏来包装函数指针调用。由于所有内容都已映射,因此检查对象只需遍历所有键即可。由于我的课程基本上尽可能接近直接使用 C++ 的 Cocoa,如果你想要类似的东西,那么我建议使用 Cocoa 文档作为蓝图。

      【讨论】:

      • 嘿,@迈克尔;你还有这个的源代码,还是你摆脱了它?如果您不介意,我想看看它。
      • 哎呀,你的名字拼错了!不奇怪我从来没有得到回复......
      【解决方案11】:

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

      接口类似于 C# 中的反射,无需任何 RTTI 即可工作。

      【讨论】:

        【解决方案12】:

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

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

        2) 如果您有权访问编译器对象,另一种选择是使用DIA SDK。如果我没记错的话,这可以让你打开 pdbs,它应该包含你的 C++ 类型的元数据。做你需要的可能就足够了。例如,This page 展示了如何获取类的所有基本类型。

        不过,这两种解决方案都有些难看!没有什么比一点 C++ 更能让您欣赏 C# 的奢华了。

        祝你好运。

        【讨论】:

        • 这很狡猾,而且是一个巨大的黑客,你在那里建议的 DIA SDK 东西。
        【解决方案13】:

        这个问题现在有点老了(不知道为什么我今天一直在问老问题)但我在想BOOST_FUSION_ADAPT_STRUCT 它引入了编译时反射。

        当然,这取决于您将其映射到运行时反射,这不会太容易,但是在这个方向上是可能的,但在相反的方向上是不可能的:)

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

        【讨论】:

        • 由minghua(最初编辑该帖子):我研究了这个BOOST_FUSION_ADAPT_STRUCT解决方案,最终想出了一个例子。请参阅这个较新的 SO 问题 - C++ iterate into nested struct field with boost fusion adapt_struct
        • 太好了,马修!刚刚意识到在过去一年中到处都看到了你的提示。直到现在才注意到它们是相关的。这些非常鼓舞人心。
        【解决方案14】:

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

        【讨论】:

          【解决方案15】:

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

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

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

          我们的DMS Software Reengineering Toolkit 是由显式语言定义参数化的通用编译器技术。它具有 C、C++、Java、COBOL、PHP 的语言定义......

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

          【讨论】:

            【解决方案16】:

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

            我想没有人提到这个:

            在 CERN,他们使用 C++ 的全反射系统:

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

            【讨论】:

            【解决方案17】:

            Ponder 是一个 C++ 反射库,用于回答这个问题。我考虑了这些选项并决定自己制作,因为我找不到一个能满足我所有条件的选项。

            虽然这个问题有很好的答案,但我不想使用大量的宏,也不想依赖 Boost。 Boost 是一个很棒的库,但是有很多小型定制 C++0x 项目更简单,编译时间更快。能够在外部装饰一个类也有好处,比如包装一个(还没有?)支持 C++11 的 C++ 库。它是 CAMP 的分支,使用 C++11,不再需要 Boost

            【讨论】:

              【解决方案18】:

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

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

              【讨论】:

                【解决方案19】:

                如果您正在寻找相对简单的 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 );
                
                }
                

                REFLECTABLE 定义使用类名 + 字段名和offsetof - 来识别特定字段在内存中的位置。我尝试尽可能多地使用 .NET 术语,但 C++ 和 C# 是不同的,所以不是一对一的。整个 C++ 反射模型位于 TypeInfoFieldInfo 类中。

                我使用 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。可以将它移植到其他操作系统/编译器,但还没有做到这一点。 (问我是否真的喜欢解决方案,我也许可以帮助你)

                此方案适用于一个类多子类的一次序列化。

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

                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

                但由于主题非常高级 - 我建议先检查视频。

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

                【讨论】:

                  【解决方案20】:

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

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

                  遍历字段可以像...一样简单

                  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 Library 建立在 RandomAccessReflection 之上,它自动识别用于读取或写入的适当 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);
                  }
                  

                  上面可以这样运行...

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

                  另见...

                  【讨论】:

                    【解决方案21】:

                    当我想用 C++ 进行反射时,我阅读了 this article 并改进了我在那里看到的内容。对不起,没有可以。我不拥有结果......但你当然可以得到我所拥有的并从那里开始。

                    我目前正在研究使用inherit_linearly 的方法来简化可反射类型的定义。实际上我已经走得很远了,但我还有很长的路要走。 C++0x 的变化很可能在这方面有很大的帮助。

                    【讨论】:

                      【解决方案22】:

                      看起来 C++ 还没有这个特性。 C++11 也推迟了反思((

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

                      【讨论】:

                        【解决方案23】:

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

                        这篇文章非常详细地解释了如何实现一个非常简单和基本的反射系统。授予它不是最健康的解决方案,还有一些粗糙的边缘需要整理,但对于我的需要来说已经足够了。

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

                        【讨论】:

                          【解决方案24】:

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

                          【讨论】:

                          • 欢迎来到 Stack Overflow。虽然这个答案是主题,但重要的是要指出您是该软件的作者,以明确这不是一个公正的建议:-)
                          【解决方案25】:

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

                          【讨论】:

                            【解决方案26】:

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

                            【讨论】:

                              【解决方案27】:

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

                              【讨论】:

                                【解决方案28】:

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

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

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

                                对此的唯一要求(dlopen()libdldlfcn.h 除外)是知道函数的参数和类型。

                                【讨论】:

                                  猜你喜欢
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2020-04-01
                                  • 2015-01-12
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多