在C++上实现反射用法

0. 简介

最近看很多端到端的工作,发现大多数都是基于mmdet3d来做的,而这个里面用的比较多的形式就是反射机制,这样其实可以比较好的通过类似plugin的形式完成模型模块的插入。当然我们这里不是来分析python的反射机制的。我们这篇文章主要来介绍C++上实现反射。

1. 反射的用途

一般来说就是序列化反序列化啦。比如说你想通过网络传递一个实例,或者把它保存到文件里,以后再取出来放到程序里,这就需要反射

反射其实还能细分为静态反射和动态反射

  1. 静态反射,就是在编译期生成反射信息
  2. 动态反射,就是在运行时生成反射信息
  3. 动态反射,显然需要一套强大的运行时和动态类型系统,也是显然的很复杂

在来,还有侵入式非侵入式之分。非侵入式的反射允许不对源码进行修改就能实现反射;侵入式呢就得对源码动动手脚了

第一种:实现思路,是在源码里加入大量的反射信息,手动注册反射。 这种库的代表是rttr

第二种:实现思路是通过parser解析源码,自动生成反射信息。
这种库的代表是QT,UE的反射系统

第三种: 用大量的编译期模板生成元信息,然后构建一套巨抽象的运行时,比如Ubpa/UDRefl

第四种: 利用调试器的运行时信息来生成反射代码,这种想法并非无稽之谈,思考下,lldb,gdb明显能在运行时获取字段,内容,类型

第五种: 绑架编译器! clang提供了插件功能。事实上也有大佬做了,这些都有比较详细的例子。

2. 源码添加,手动注册

这种用的是比较多的,一般的是自定义一个反射类,然后用模板来实现一个模板类管理类名和类构造函数的映射关系,并提供构造对象的接口,每个基类需要初始化一个这样的管理对象。

下面我们提供一个对应的 static 模板函数,用来保存和返回对应的管理对象。并使用模板函数和 new 操作符作为每个类的构造函数。
实现一个简单的 helper 模板类提供作为注册的简单封装,并封装宏实现注册。下面是具体代码:

#ifndef __BASE_H__
#define __BASE_H__
#include <string>
#include <map>
#include <iostream>// 使用模板,每个基类单独生成一个 ClassRegister
// 好处是需要反射的类不需要去继承 Object 对象
// ClassRegister 用来管理类名->类构造函数的映射,对外提供根据类名构造对象对函数
template<typename ClassName>
class ClassRegister {public:typedef ClassName* (*Constructor)(void);private:typedef std::map<std::string, Constructor> ClassMap;ClassMap constructor_map_;public:// 添加新类的构造函数void AddConstructor(const std::string class_name, Constructor constructor) {typename ClassMap::iterator it = constructor_map_.find(class_name);if (it != constructor_map_.end()) {std::cout << "error!";return;}constructor_map_[class_name] = constructor;}// 根据类名构造对象ClassName* CreateObject(const std::string class_name) const {typename ClassMap::const_iterator it = constructor_map_.find(class_name);if (it == constructor_map_.end()) {return nullptr;}return (*(it->second))();}
};// 用来保存每个基类的 ClassRegister static 对象,用于全局调用
template <typename ClassName>
ClassRegister<ClassName>& GetRegister() {static ClassRegister<ClassName> class_register;return class_register;
}// 每个类的构造函数,返回对应的base指针
template <typename BaseClassName, typename SubClassName>
BaseClassName* NewObject() {return new SubClassName();
}// 为每个类反射提供一个 helper,构造时就完成反射函数对注册
template<typename BaseClassName>
class ClassRegisterHelper {public:ClassRegisterHelper(const std::string sub_class_name,typename ClassRegister<BaseClassName>::Constructor constructor) {GetRegister<BaseClassName>().AddConstructor(sub_class_name, constructor);}~ClassRegisterHelper(){}
};// 提供反射类的注册宏,使用时仅提供基类类名和派生类类名
#define RegisterClass(base_class_name, sub_class_name) \static ClassRegisterHelper<base_class_name> \sub_class_name##_register_helper( \#sub_class_name, NewObject<base_class_name, sub_class_name>);// 创建对象的宏
#define CreateObject(base_class_name, sub_class_name_as_string) \GetRegister<base_class_name>().CreateObject(sub_class_name_as_string)#endif

下面是使用的示例:

#include <iostream>
#include <memory>
#include <cstring>
#include "base3.h"
using namespace std;class base
{public:base() {}virtual void test() { std::cout << "I'm base!" << std::endl; }virtual ~base() {}
};class A : public base
{public:A() { cout << " A constructor!" << endl; }virtual void test() { std::cout << "I'm A!" <<std::endl; }~A() { cout << " A destructor!" <<endl; }
};// 注册反射类 A
RegisterClass(base, A);class B : public base
{public :B() { cout << " B constructor!" << endl; }virtual void test() { std::cout << "I'm B!"; }~B() { cout << " B destructor!" <<endl; }
};// 注册反射类 B
RegisterClass(base, B);class base2
{public:base2() {}virtual void test() { std::cout << "I'm base2!" << std::endl; }virtual ~base2() {}
};class C : public base2
{public :C() { cout << " C constructor!" << endl; }virtual void test() { std::cout << "I'm C!" << std::endl; }~C(){ cout << " C destructor!" << endl; }
};// 注册反射类 C
RegisterClass(base2, C);int main()
{// 创建的时候提供基类和反射类的字符串类名base* p1 = CreateObject(base, "A");p1->test();delete p1;p1 = CreateObject(base, "B");p1->test();delete p1;base2* p2 = CreateObject(base2, "C");p2->test();delete p2;return 0;
}

3. parser解析源码,自动生成反射信息

要实现自动生成反射信息的功能,我们需要编写一个代码解析器,用于解析源代码并提取出需要的信息。一般来说,代码解析器会将源码转换为一棵抽象语法树(AST),然后对这棵树进行遍历,提取出需要的信息。

要使用Clang来解析源码并自动生成反射信息,可以借助Clang的AST(Abstract Syntax Tree)来实现。以下是一个简单的示例代码,演示如何使用Clang来解析源码并生成反射信息:

#include <iostream>
#include <string>
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/Tooling.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"using namespace clang;
using namespace clang::tooling;
using namespace clang::ast_matchers;class ReflectionGenerator : public MatchFinder::MatchCallback {
public:virtual void run(const MatchFinder::MatchResult &Result) {if (const CXXRecordDecl *Record = Result.Nodes.getNodeAs<CXXRecordDecl>("class")) {std::string className = Record->getNameAsString();std::cout << "Registering class: " << className << std::endl;// 在这里可以生成反射信息并注册类// 获取类名,并输出到控制台std::cout << "Class name: " << className << std::endl;// 遍历类的字段,并输出到控制台for (const FieldDecl *Field : Record->fields()) {std::string fieldName = Field->getNameAsString();std::cout << "Field name: " << fieldName << std::endl;}}}
};int main(int argc, const char **argv) {CommonOptionsParser OptionsParser(argc, argv);ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());ReflectionGenerator Generator;MatchFinder Finder;Finder.addMatcher(cxxRecordDecl().bind("class"), &Generator);return Tool.run(newFrontendActionFactory(&Finder).get());
}

在这个示例代码中,我们通过Clang的AST来解析源码,并使用AST Matchers来匹配C++类的声明。当匹配到一个类声明时,ReflectionGenerator类的run方法会被调用,我们可以在这里生成反射信息并注册类。下面我们对上面代码中主要部分进行详细解释:

  1. ReflectionGenerator类:这是一个继承自MatchFinder::MatchCallback的自定义类,用于处理匹配到的AST节点。在run方法中,根据匹配到的C++类定义,获取类名并输出到控制台,同时遍历类的字段并输出字段名。

  2. main函数

    • 创建CommonOptionsParser对象和ClangTool对象,用于解析命令行参数和运行Clang工具。
    • 创建ReflectionGenerator对象和MatchFinder对象,用于注册匹配规则和处理匹配结果。
    • 通过Tool.run方法运行Clang工具,并传入匹配规则和处理结果的工厂对象。
  3. cxxRecordDecl matcher:使用Finder.addMatcher添加了一个匹配规则,用于匹配C++类的定义。当匹配到符合规则的AST节点时,会调用ReflectionGeneratorrun方法进行处理。

  4. 反射信息生成:在ReflectionGeneratorrun方法中,获取到类名和字段名后,输出到控制台。这里展示了获取类名和字段名的基本操作,您可以根据需求进一步扩展生成反射信息的逻辑。

此外这里我们可以通过attribute((annotate(…))) 来完成相同的操作。__attribute__((annotate(...))) 的意义是为代码中的类、字段、函数等元素添加自定义的元数据信息。这些信息可以用于实现反射、元编程、代码生成等功能。通过注解,我们可以为代码中的各种元素添加描述、标签、类型信息等,使其更具有可读性和可维护性,同时也可以在程序运行时动态地获取这些信息并进行相应的操作。在下面的示例中,我们使用了 __attribute__((annotate("reflect_class", "BarClass")))__attribute__((annotate("reflect_property", "int foo")))__attribute__((annotate("reflect_func", "void setFoo(int)")) 等注解来为类、字段和函数添加反射信息。这些注解可以帮助我们在编译时或运行时识别和操作这些元素,实现更高级的功能。

以下是一个完整的示例代码,其中使用了属性拓展和注解来实现反射功能:

#include <iostream>#define RFL_CLASS(...) __attribute__((annotate("reflect_class", #__VA_ARGS__)))
#define RFL_PROPERTY(...) __attribute__((annotate("reflect_property", #__VA_ARGS__)))
#define RFL_FUNC(...) __attribute__((annotate("reflect_func", #__VA_ARGS__)))class RFL_CLASS("BarClass") Bar {
public:RFL_PROPERTY("int foo") int foo;RFL_FUNC("void setFoo(int)") void setFoo(int value) {foo = value;}RFL_FUNC("int getFoo()") int getFoo() {return foo;}
};int main() {Bar bar;bar.setFoo(42);std::cout << "Value of foo: " << bar.getFoo() << std::endl;return 0;
}
  • RFL_CLASS 宏和类定义中,我们添加了一个字符串参数,用于指定类的名称。这样可以在反射时更准确地标识类。
  • RFL_PROPERTY 宏和字段定义中,我们添加了一个字符串参数,用于指定字段的类型和名称。这样可以在反射时更准确地标识字段。
  • RFL_FUNC 宏和成员函数定义中,我们添加了一个字符串参数,用于指定函数的签名。这样可以在反射时更准确地标识函数。

在main函数中,我们创建了一个Bar对象,并使用setFoo和getFoo函数来设置和获取foo字段的值。这些函数的签名和类/字段的信息都被注解添加到了代码中,以便在反射时能够准确地识别和访问它们。

3. 元信息结构反射机制

要在C++中构建一套可以在运行时使用的反射机制,你可以利用模板元编程在编译期生成类型元信息,并在运行时通过这些元信息进行反射操作。

首先,我们需要定义一种数据结构来存储类型的元信息。

#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <unordered_map>
#include <vector>struct FieldInfo {std::string name;std::type_index type;size_t offset;
};struct TypeInfo {std::string name;std::vector<FieldInfo> fields;
};

然后,我们需要一个机制来自动生成这些元信息。这里我们用模板和宏来实现这一点。

#define REFLECTABLE(...) \friend struct Reflection; \static void reflect(Reflection& r) { \r.registerType(typeid(*this), #__VA_ARGS__, __VA_ARGS__); \}class Reflection {
public:template<typename T>void registerType(std::type_index type, const std::string& fieldNames, T& instance) {std::istringstream stream(fieldNames);std::string fieldName;size_t offset = 0;while (std::getline(stream, fieldName, ',')) {trim(fieldName);TypeInfo& typeInfo = typeRegistry[type];typeInfo.name = type.name();typeInfo.fields.push_back({ fieldName, typeid(instance. * (T::*)(T:: *) &fieldName), offset });offset += sizeof(fieldName);}}template<typename T>const TypeInfo& getTypeInfo() {return typeRegistry[std::type_index(typeid(T))];}private:std::unordered_map<std::type_index, TypeInfo> typeRegistry;void trim(std::string& s) {s.erase(0, s.find_first_not_of(' '));s.erase(s.find_last_not_of(' ') + 1);}
};

现在,我们可以定义一个类,并使用宏来使其成为可反射的。

class MyClass {
public:int x;float y;std::string z;REFLECTABLE(x, y, z)
};

4. 使用反射机制

最后,我们可以在运行时使用反射机制来访问类的元信息。

int main() {MyClass obj;Reflection reflection;// 注册类型信息obj.reflect(reflection);// 获取类型信息const TypeInfo& typeInfo = reflection.getTypeInfo<MyClass>();// 输出字段信息std::cout << "Type: " << typeInfo.name << std::endl;for (const auto& field : typeInfo.fields) {std::cout << "Field: " << field.name << ", Type: " << field.type.name() << ", Offset: " << field.offset << std::endl;}return 0;
}
  1. FieldInfo 和 TypeInfo 结构体:这些结构体用于存储字段和类型的元信息。
  2. Reflection 类:这个类负责注册和存储类型元信息,并提供查询接口。
  3. REFLECTABLE 宏:这个宏用于简化类型元信息的注册过程。它声明一个友元函数,这个函数可以访问类的私有成员,并在编译期生成字段的名字和类型信息。
  4. registerType 和 getTypeInfo 方法
    • registerType 方法在编译期处理传入的字段名称,生成对应的字段信息并存储在一个哈希表中。
    • getTypeInfo 方法在运行时查询并返回存储的类型信息。
  5. MyClass 类:这是一个简单的示例类,通过 REFLECTABLE 宏声明它是可反射的。
  6. main 函数:在 main 函数中,我们创建一个 MyClass 的实例并注册它的类型信息,然后查询并输出这些信息。

这个例子展示了如何在 C++ 中利用模板和宏实现一个简单的反射机制。这个机制允许你在运行时访问类型的元信息,从而实现各种动态操作,例如序列化和反序列化、类型检查和动态调用等等。

5. 调试器的运行时信息形成反射

使用调试器的运行时信息来生成反射代码是一种非常高级的技术,通常需要结合调试器提供的 API 和脚本语言(例如 Python)进行自动化处理。这个过程大致可以分为以下几个步骤:

  1. 使用调试器获取目标程序的运行时信息
  2. 解析并提取元信息
  3. 生成对应的反射代码

首先,编写一个简单的 C++ 程序:

// example.cpp
#include <iostream>
#include <vector>
#include <string>class MyClass {
public:int x;float y;std::string z;void print() {std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;}
};int main() {MyClass obj;obj.x = 10;obj.y = 20.5f;obj.z = "Hello, World!";obj.print();return 0;
}

编译这个程序:

g++ -g example.cpp -o example

…详情请参照古月居

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/60359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推荐一款多物理场模拟仿真软件:STAR-CCM+

Siemens STAR-CCM是一款功能强大的计算流体力学(CFD)软件&#xff0c;由西门子公司推出。它集成了现代软件工程技术、先进的连续介质力学数值技术和卓越的设计&#xff0c;为工程师提供了一个全面的多物理场仿真平台。主要特点与优势&#xff1a;多物理场仿真、自动化与高效、高…

无人机飞手考证,地面站培训技术详解

无人机飞手考证及地面站培训技术涉及多个关键方面&#xff0c;以下是对这些方面的详细解析&#xff1a; 一、无人机飞手考证流程与要求 1. 证书类型 民用无人机驾驶员证书&#xff1a;这是国家民航局颁发的无人机操作人员资质证书&#xff0c;分为视距内驾驶员、超视距驾驶员…

SpringMVC全面复习

Javaweb SpringMVC Spring MVC是Spring框架的一个模块&#xff0c;专门用于构建Web应用程序的模型-视图-控制器&#xff08;MVC&#xff09;架构。它通过清晰的分离关注点&#xff0c;简化了Web应用各部分的开发。Spring MVC提供了强大的绑定机制&#xff0c;能够将请求参数绑定…

python基础大杂烩

命令提示符程序&#xff0c;输入python&#xff0c;运行python程序 代码通过解释器程序翻译给计算机去执行 命令提示符输入的python本质上就是调用D:/dev/python/python3.12.5/python.exe这个解释器程序 有python程序将输入的代码翻译成二进制的0和1&#xff0c;去向计算机去运…

MathGPT的原理介绍,在中小学数学教学的应用场景,以及代码样例实现

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下MathGPT的原理介绍&#xff0c;在中小学数学教学的应用场景&#xff0c;以及代码样例实现。MathGPT的核心架构是一个精心设计的多层次系统&#xff0c;旨在有效处理复杂的数学问题。其主要组成部分包括 数学知识图谱…

算法演练----24点游戏

给定4个整数&#xff0c;数字范围在1~13之间任意使用-*/&#xff08;&#xff09;&#xff0c;构造出一个表达式&#xff0c;使得最终结果为24&#xff0c; 方法一 算法分析&#xff1a;加括号和取出重复表达式 # 导入精确除法模块&#xff0c;使得在Python2中除法运算的行为更…

关于指针p有关的3个值

1&#xff0c;他的类型是int*; 2,*p是解用&#xff0c;指向的是对象 3&#xff0c;&p指向的是p的地址&#xff0c;是指针的地址

《JavaEE进阶》----20.<基于Spring图书管理系统①(登录+添加图书)>

PS&#xff1a;关于接口定义 接口定义&#xff0c;通常由服务器提供方来定义。 1.路径&#xff1a;自己定义 2.参数&#xff1a;根据需求考虑&#xff0c;我们这个接口功能完成需要哪些信息。 3.返回结果&#xff1a;考虑我们能为对方提供什么。站在对方角度考虑。 我们使用到的…

Linux服务管理-iSCSI

iSCSI 基础知识 iSCSI&#xff08;Internet Small Computer System Interface&#xff09;协议是一种基于IP网络的存储协议&#xff0c;它允许主机&#xff08;计算机或服务器&#xff09;通过TCP/IP网络访问远程存储设备。该协议具有以下主要特点&#xff1a; 灵活性&#xf…

快速掌握——python类 封装[私有属性方法]、继承【python进阶】(内附代码)

1.类的定义 与 实例化对象 在python中使用class关键字创建一个类。 举例子 class Stu(object):id 1001name 张三def __init__(self):passdef fun1(self):pass# 实例化对象 s1 Stu() s2 Stu() print(s1.name) print(s2.name) 第一个方法 __init__是一种特殊的方法&#x…

HarmonyOS 如何实现传输中的数据加密

文章目录 摘要引言数据传输加密概述选择加密算法和传输协议加密实现方案与 Demo 代码配置 HTTPS/TLSAES 加密的实现代码详解RSA加密的实现代码详解 QA环节总结参考资料 摘要 本文将介绍在 HarmonyOS 应用中如何实现数据传输的加密策略。我们将讨论常见的加密算法&#xff08;如…

Bilibili-超能用户榜入口优化-技术方案反思与总结

目录 客户端实现&#xff1a; 高能用户入口实现逻辑&#xff1a; 接口服务信息&#xff08;服务端下发&#xff09;&#xff1a; 执行方案&#xff1a; (1)数据类新增服务端下发字段 ​编辑 (2) UI添加 寻找思路&#xff1a; &#xff08;3&#xff09;超能用户icon显示…

vue实现图片无限滚动播放

本人vue新手菜鸡&#xff0c;文章为自己在项目中遇到问题的记录&#xff0c;如有不足还请大佬指正 文章目录 实现效果代码展示总结 因为刚接触vue&#xff0c;本想着看看能不能用一些element的组件实现图片的轮播效果&#xff0c;尝试使用过element-UI里的走马灯Carouse&#x…

MySQL缓存使用率超过80%的解决方法

MySQL缓存使用率超过80%的解决方法 一、识别缓存使用率过高的问题1.1 使用SHOW GLOBAL STATUS命令监控1.2 监控其他相关指标二、分析缓存使用率过高的原因2.1 数据量增长2.2 查询模式变化2.3 配置不当三、解决缓存使用率过高的方法3.1 调整Buffer Pool大小3.1.1 计算合理的Buff…

LeetCode【0036】有效的数独

本文目录 1 中文题目2 求解方法&#xff1a;python内置函数set2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 请根据以下规则判断一个 9 x 9 的数独是否有效。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线…

UNIAPP发布小程序调用讯飞在线语音合成+实时播报

语音合成能够将文字转化为自然流畅的人声&#xff0c;提供100发音人供您选择&#xff0c;支持多语种、多方言和中英混合&#xff0c;可灵活配置音频参数。广泛应用于新闻阅读、出行导航、智能硬件和通知播报等场景。 在当下大模型火爆的今日&#xff0c;语音交互页离不开语音合…

成都睿明智科技有限公司解锁抖音电商新玩法

在这个短视频风起云涌的时代&#xff0c;抖音电商以其独特的魅力迅速崛起&#xff0c;成为众多商家争夺的流量高地。而在这片充满机遇与挑战的蓝海中&#xff0c;成都睿明智科技有限公司犹如一颗璀璨的新星&#xff0c;以其专业的抖音电商服务&#xff0c;助力无数品牌实现从零…

Llama旋转位置编码代码实现及详解

旋转位置编码RoPE 在旋转位置编码与Transformer和BERT之间的区别中介绍了旋转位置编码&#xff08;RoPE&#xff09;的特点和优势&#xff0c;这种输入长度动态可变的优势使得在Llama编码时&#xff0c;不需要掩码将多余的嵌入掩住。为了详细了解RoPE是如何实现的&#xff0c;…

遇到“msvcr120.dll丢失”要怎么解决?详解msvcr120.dll的解决方法

遇到“msvcr120.dll丢失”错误通常表明你的系统缺少一个关键的DLL文件&#xff0c;这是Microsoft Visual C 2013的一部分。这个问题可能导致某些程序无法运行&#xff0c;影响电脑性能。不过&#xff0c;解决这一问题并不复杂。接下来&#xff0c;本文将向你展示几种简单的修复…

【机器学习】平均绝对误差(MAE:Mean Absolute Error)

平均绝对误差 (Mean Absolute Error, MAE) 是一种衡量预测值与实际值之间平均差异的统计指标。它在机器学习、统计学等领域中广泛应用&#xff0c;用于评估模型的预测精度。与均方误差 (MSE) 或均方误差根 (RMSE) 不同&#xff0c;MAE 使用误差的绝对值&#xff0c;因此它在处理…