【问题标题】:"Poor Man's Reflection" (AKA bottom-up reflection) in C++C++ 中的“穷人的反射”(AKA 自下而上的反射)
【发布时间】:2014-01-11 02:44:48
【问题描述】:

我在 C++ 中为超模块化架构实现了一些基本反射,其中几乎所有功能都作为插件加载并在运行时动态解释。由于系统有一个独特的结构自组织元素,因此组件需要一些相互检查的方法(有关需要这种反射风格的示例,请参阅此问题:“Best fit” dynamic type matching for plugins in C++)。

到目前为止,该架构是用 C# 开发的,但我现在正在研究如何用 C++ 来实现它。至此,我已经根据以下模型为“穷人的倒影”创建了骨架:

一个Type类来保存相关的类信息:

namespace Reflection {

    class Type {
    public:
        Type(Object &, string, bool(*)(Type *));
        ~Type();
        Object & RefObj();
        string Name();
        bool IsAssignableFrom(Type *);
    private:
        Object & _refObj;
        string _name;
        bool(*_isAssignableFrom_Handler)(Type *);
    };

}

还有一个 Object 类,反射模型中的所有参与者都将从该类中下降:

class Object {
public:
    Object();
    virtual ~Object();
    virtual string ToString();
    virtual Reflection::Type * GetType();
    static Reflection::Type * Type();
    static bool IsAssignableFrom(Reflection::Type *);
private:
    static Object _refObj;
    static Reflection::Type _type;
};

.. 定义如下:

string Object::ToString() { return GetType()->Name(); }

// all derived classes must implement the equivalent of this:

Reflection::Type * Object::GetType() { return &_type; }

Object Object::_refObj;

Reflection::Type Object::_type(_refObj, "Object", Object::IsAssignableFrom);

Reflection::Type * Object::Type() { return &_type; }

bool Object::IsAssignableFrom(Reflection::Type * type) {
    return dynamic_cast<Object*>(&type->RefObj()) != nullptr;
}

请注意,我只需要我的反射率在我自己的类层次结构中运行(所有类都继承自 Object)。因此,上面的代码现在使我能够:

  • 获取任意实例的类型:instance.GetType()

  • 获取任意类的Type:class::Type()

  • 比较类型:例如(instance.GetType() == class::Type())(instanceA.GetType() == instanceB.GetType())

  • 执行运行时检查以查看是否可以为一个类型实例分配另一个(即动态地,使用两个“未知数”。C++ 中的所有内置选项似乎都需要在编译时知道至少一种类型) , 基本上相当于is 和推断继承关系的关键:(instanceA.GetType()-&gt;IsAssignableFrom(instanceB.GetType()))

  • 按类型动态引用抽象类型

  • 获取一致、友好的类型名称(即类)

这足以满足我的即时需求,并且可以通过向 Type 类添加功能来扩展功能范围(接下来是实例化仅给定 Type 实例的类的能力 - 类似于 .Net 的Activator.CreateInstance)。但与“正确”反射不同,后者本质上是一种自上而下的方法,其中关于类的 [元] 信息在编译器级别收集/集中管理,这是手动和自下而上完成的,将知识分配到对象本身并给出它们是在运行时相互通信的一种方式。因此,要实现这一点,该系统中包含的每个类都需要实现与Object 类相同的成员和功能,以封装自身的相关方面以“导出”。例如,Plugin 类看起来像这样(定义):

Reflection::Type * Plugin::GetType() { return &_type; }

Plugin Plugin::_refObj;

Reflection::Type Plugin::_type(_refObj, "Plugin", Plugin::IsAssignableFrom);

Reflection::Type * Plugin::Type() { return &_type; }

bool Plugin::IsAssignableFrom(Reflection::Type * type) {
    return dynamic_cast<Plugin*>(&type->RefObj()) != nullptr;
}

如您所见,它实际上与其父类Object 相同。几乎所有这些函数仅因它们的类类型(和名称)而异。

所以我有几个问题。

  • 第一个是是否有任何方法可以简化这一点,通过编译器宏或巧妙的继承/模板等,因为有/将会有很多重复。它让我觉得可以自动化?就像提取类的名称(可能包括名称空间)并从中生成代码一样?或者一些基于一个或两个变量的源代码 sn-p 模板(想到类名)。

  • 第二个更通用(也是我包含所有这些代码的原因)。我只使用 C++ 很短的时间,所以我觉得很不合时宜,并假设我的方法和实现细节可能非常幼稚。如果有其他人研究过类似的架构/有类似的需求,也许他们可以分享他们的经验(或者甚至只是指出我的模型/代码中的缺陷)。

有关需要这种反射风格的情况示例,请参阅以下问题:“Best fit” dynamic type matching for plugins in C++

更新:

就第一个问题而言,这是我最终要做的:

我制作了两个宏,一个用于 .h 文件,一个用于 .cpp 文件。现在我需要做的就是在类声明中添加REFLECTION_H(TYPE_NAME),在类定义中添加REFLECTION_CPP(TYPE_NAME),所有反射样板都会自动包含在内。然后,我可以像往常一样添加特定于类的成员,而不必考虑反射,因为我知道所需的所有管道都已到位并且一切正常。例如,在当前实现中,一个新的 Surface 类现在看起来像这样:

Surface.h:

class Surface : public Plugin
{
    REFLECTION_H(Surface)
public:
    // ...class specific public member declarations...
private:
    // ...class specific private member declarations...
};

Surface.cpp:

REFLECTION_CPP(Surface);

Surface::~Surface() {}

// ...class specific member definitions...

宏定义如下:

#define REFLECTION_H(TYPE_NAME) \
    public:\
    virtual ~TYPE_NAME();\
    static Reflection::Type& Type();\
    private:\
    virtual Reflection::Type& _getType();\
    static TYPE_NAME _refObj;\
    static Reflection::Type _type;\
    static bool IsAssignableFrom(Reflection::Type&);\
    static plugin_ptr CreateInstance();

#define REFLECTION_CPP(TYPE_NAME) \
    Reflection::Type& TYPE_NAME::Type() { return _type; }\
    Reflection::Type& TYPE_NAME::_getType() { return _type; }\
    TYPE_NAME TYPE_NAME::_refObj;\
    Reflection::Type TYPE_NAME::_type(_refObj, #TYPE_NAME, true, IsAssignableFrom, CreateInstance);\
    bool TYPE_NAME::IsAssignableFrom(Reflection::Type& type) { return dynamic_cast<TYPE_NAME*>(&type.RefObj()) != nullptr; }\
    plugin_ptr TYPE_NAME::CreateInstance() { return plugin_ptr(new TYPE_NAME); }

【问题讨论】:

  • 这是托管 C++ ala C++.NET 吗?
  • 不,只是简单的 C++(尽管我可能(故意)使用 C# 中有关命名和格式等的一些约定,以使自己更舒服)。
  • C++ 的设计初衷并不是像 C# 或 Java 那样使用反射(即运行时类型信息)。
  • 那么,您为什么不使用当今所有主要操作系统(以及次要操作系统)都可用的标准“组件架构”之一呢?或者也许这只是学习这些先进技术的练习?无论如何,我建议看看现有的 C++ 库,例如 ROOT.Reflex (root.cern.ch/drupal/content/reflex) 或 Boost.Reflex bytemaster.github.io/boost_reflect/index.html,尽管后者似乎已被放弃。
  • 没人推荐crtp?我没有看到任何困难。

标签: c++ design-patterns plugins reflection types


【解决方案1】:

总结一下:

  1. 有很多现有的行业标准框架可以在 C++ 中实现可加载模块,具有不同级别的模块自省。仅举几例:MSWindows COM 和变体、CORBA(各种实现)、KDE ​​KParts、Linux 和其他类 Unix 操作系统上支持 DBus 的服务等。一般来说,我会根据目标平台和其他方式选择现有变体之一考虑因素

  2. 如果您绝对需要构建自己的bike^W 框架,我会将实现模块业务逻辑的类与样板分开。当然,这种方法引入了另一个级别的间接性,这可能会导致一些性能问题。但如果做得聪明,这个样板可能会很薄,几乎无法察觉。此外,将 BL 与框架分离也将允许在未来完全改变马匹而无需付出太多努力。要采用这种方法,我会选择代码操作工具,如 GCC-XML 或适当的 CLang 模块。

  3. 还有许多现有的 C++ 库,从简单到复杂,可以构建您自己的紧密组合的框架。示例:ROOT ReflexBoost.Reflect

其余的由您自己选择。我知道 Gnome 项目的人们不满意 C++ 的不足和缺点,他们在纯 C (GLib/GObject) 上发明了自己的 OOP 框架,后来在此基础上开发了一种类似于 C# (Vala) 的新的全功能语言。这完全取决于你在哪里停下来:)

【讨论】:

    【解决方案2】:

    Python

    由于您提到您在 VisualStudio 中,我在下面编写的宏可能不适合您(根据我的经验,宏可能是令人讨厌的跨平台)。因此,这里有一个 Python 示例,您可以将其作为预构建脚本运行,根据文本文件中的名称生成 *.cpp 文件。

    create_classes.py

    template = """Reflection::Type * {0}::GetType() {{
        return &_type;
    }}
    
    // static type info
    
    {0} {0}::_refObj;
    
    Reflection::Type {0}::_type(_refObj, "{0}", {0}::IsAssignableFrom);
    
    Reflection::Type * {0}::Type() {{
        return &_type;
    }}
    
    bool {0}::IsAssignableFrom(Reflection::Type * type) {{
        return dynamic_cast<{0}*>(&type->RefObj()) != nullptr;
    }}
    """
    
    if __name__ == '__main__':
        with open('classes', 'r') as classes:
            for class_name in classes:
                class_name = class_name.strip()
                with open('{0}.cpp'.format(class_name), 'w') as source:
                    source.write(template.format(class_name))
    

    (文本文件)

    Blossom
    Bubbles
    Buttercup
    

    这将使用上面的模板创建 Blossom.cpp、Bubbles.cpp 和 Buttercup.cpp。将正确的名称放入“类”文本文件取决于您。 :)

    我相信您可以调整它以将每个定义拆分为 *.hpp 和 *.cpp,如果这有帮助,请告诉我。

    C++ 宏

    尽管我不喜欢宏(我仍然建议尽可能不要使用它们!)这里有一些宏会生成你的代码。我还没有对它们进行彻底的测试,所以它们可能会射中你的脚。它们的命名也很糟糕(从它们的名称中不清楚宏的作用),但这就是想法。

    ma​​cros.cpp

    #define GET_TYPE_METHOD(X) \
        Reflection::Type * X::GetType() { return &_type; }
    
    #define GET_REF_OBJ(X) X X::_refObj;
    
    #define GET_UNDERSCORE_TYPE(X) \
        Reflection::Type X::_type(_refObj, #X, X::IsAssignableFrom);
    
    #define GET_TYPE(X) \
        Reflection::Type * X::Type() { return &_type; }
    
    #define GET_IS_ASSIGNABLE_FROM(X) \
        bool X::IsAssignableFrom(Reflection::Type * type) { return dynamic_cast<X*>(&type->RefObj()) != nullptr; }
    
    GET_TYPE_METHOD(Keeler)
    GET_REF_OBJ(Keeler)
    GET_UNDERSCORE_TYPE(Keeler)
    GET_TYPE(Keeler)
    GET_IS_ASSIGNABLE_FROM(Keeler)
    

    如果你运行g++ -E macros.cpp,你会得到预处理器的输出。看看预处理器是怎么想的:

    $ g++ -E macros.cpp
    # 1 "macros.cpp"
    # 1 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 1 "<command-line>" 2
    # 1 "macros.cpp"
    # 16 "macros.cpp"
    Reflection::Type * Keeler::GetType() { return &_type; }
    Keeler Keeler::_refObj;
    Reflection::Type Keeler::_type(_refObj, "Keeler", Keeler::IsAssignableFrom);
    Reflection::Type * Keeler::Type() { return &_type; }
    bool Keeler::IsAssignableFrom(Reflection::Type * type) { return dynamic_cast<Keeler*>(&type->RefObj()) != nullptr; }
    

    这是否符合您的要求?

    【讨论】:

    • 是的,这看起来很有希望 :) 我正在使用 Visual Studio,如果这有什么不同的话。我在这里看到,您基本上为每个函数/成员定义制作了一个宏。我假设可以将它们制成一个宏来定义它们(即,一个用于 .h,一个用于 .cpp)。
    • 未经 VisualStudio 测试,抱歉。编辑以在 Python 中提供解决方案。
    • @d7samurai 我不相信 VisualStudio 中的那些宏(只是为了安全起见),所以我添加了一个 Python 脚本来为您创建类。您可以将其用作预构建脚本。至于在 .hpp 和 .cpp 文件之间进行拆分,您可以根据自己的喜好调整 python 脚本。
    • 我接受了您的回答,因为我最终将它用作构建宏的参考点。请参阅我的最终实施的问题更新。
    • 很好,很高兴我能帮上忙。祝你的项目好运。
    【解决方案3】:

    你可以试试这个。。也可以看看:http://en.cppreference.com/w/cpp/header/type_traits

    #include <iostream>
    #include <type_traits>
    
    #ifdef _MSC_VER
    #define __FUNC_NAME__ __FUNCTION__
    #else
    #define __FUNC_NAME__ __PRETTY_FUNCTION__
    #endif // _MS_VER
    
    #ifdef _MSC_VER
    #define RF_DETAIL  std::string GetDetail() {return __FUNCSIG__;}
    #else
    #define RF_DETAIL  std::string GetDetail() {return __FUNC_NAME__;}
    #endif
    
    
    #define RF_CLASS   std::string GetClass() {\
                             const std::string name = __FUNC_NAME__;\
                             std::string::size_type beg = name.find_first_of(" "), end = name.find_first_of("<");\
                             if (!end) end = name.find_first_of("::");\
                             return name.substr(beg + 1, end - beg - 1); }
    
    #define RF_TYPE    auto GetType() -> std::remove_reference<decltype(*this)>::type {return std::move(std::remove_reference<decltype(*this)>::type()); }
    
    #define RF_TYPE_PTR    auto GetTypePtr() -> decltype(this) {return this;}
    
    template<typename T>
    class Foo
    {
        public:
            RF_DETAIL;
            RF_CLASS;
            RF_TYPE;
            virtual ~Foo(){}
    };
    
    template<typename T>
    class Meh : public Foo<T>
    {
        public:
            RF_DETAIL;
            RF_CLASS;
            RF_TYPE;
            virtual ~Meh(){}
    };
    
    class Go
    {
        public:
            RF_DETAIL;
            RF_CLASS;
            RF_TYPE;
            RF_TYPE_PTR;
    };
    
    int main()
    {
        Foo<int> f;
        std::cout<<f.GetDetail()<<"\n\n";
        std::cout<<f.GetClass()<<"\n\n\n";
    
        Meh<int> g;
        std::cout<<g.GetDetail()<<"\n\n";
        std::cout<<g.GetClass()<<"\n";
    
    
        Goo h;
        decltype(h.GetType()) i;
        std::cout<<i.GetClass();
        return 0;
    }
    

    打印:

    std::string Foo<T>::GetDetail() [with T = int; std::string = std::basic_string<c
    har>]
    
    Foo
    
    
    std::string Meh<T>::GetDetail() [with T = int; std::string = std::basic_string<c
    har>]
    
    Meh
    
    
    Foo::GetClass()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-31
      • 2011-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 2012-02-29
      相关资源
      最近更新 更多