"Poor Man's Reflection"(又名自下而上的反射)C++

"Poor Man's Reflection" (AKA bottom-up reflection) in C++

本文关键字:自下而上 反射 C++ Reflection Man Poor      更新时间:2023-10-16

我正在C++为超模块化架构实现一些基本的反射,其中几乎所有功能都作为插件加载并在运行时动态解释。由于系统有一个独特的结构自组织元素,组件需要一些相互检查的方法(有关需要这种反射风格的情况的示例,请参阅此问题: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()

  • 获取任何类的类型:class::Type()

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

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

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

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

这足以满足我的即时需求,并且可以通过向 Type 类添加功能来扩展功能范围(接下来是实例化刚给定 Type 实例的类的能力 - 模拟到 .网的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.几乎所有这些函数仅因其类类型(和名称)而异。

所以我有几个问题。

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

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

有关需要这种反射风格的情况示例,请参阅此问题:C++中插件的"最佳拟合"动态类型匹配。

更新:

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

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

表面.h:

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

表面.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); }

总结一下:

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

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

  3. 此外,还有许多现有的C++库,从简单到复杂,可以构建您自己的紧密组合框架。示例:根反射、增强反射

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

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

这将使用上面的模板创建开花.cpp、泡泡.cpp和毛茛.cpp。将正确的名称放入"类"文本文件中取决于您。:)

我相信您可以调整它以将每个定义拆分为 *.hpp 和 *.cpp,让我知道这是否有帮助。

C++宏

尽管我不喜欢宏(我仍然建议尽可能不要使用它们!),但这里有可以生成代码的宏。我还没有彻底测试过他们,所以他们可能会在你脚上开枪。它们的名字也很糟糕(不清楚宏从它们的名字中做了什么),但这就是想法。

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

这符合您要找的吗?

你可以试试这个。另请参阅: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()<<"nn";
std::cout<<f.GetClass()<<"nnn";
Meh<int> g;
std::cout<<g.GetDetail()<<"nn";
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()