【C++ 仿 MFC 反射系统】

C++ 仿 MFC 反射系统实现:支持动态创建与成员函数调用

一、背景

在 C++ 开发中,反射(Reflection)是一项强大但语言本身不支持的特性。许多框架(如 MFC、Qt)通过各自的宏和运行时类型信息(RTTI)机制实现了类似反射的功能。本文参考 MFC 的 CRuntimeClass 设计思想,实现一套轻量级、可复用的反射系统,具备以下能力:

  • 通过类名字符串动态创建对象。
  • 运行时类型判断(IsKindOf)。
  • 支持带任意参数和返回值的成员函数动态调用 (使用 std::any 类型擦除)。
  • 宏自动化注册,无需手动编写重复代码。

完整代码基于标准 C++17,不依赖 Qt 等大型框架,可直接集成到现有项目中。

二、设计思路

2.1 运行时类信息(CRuntimeClass)

每个需要反射的类都对应一个静态 CRuntimeClass 对象,存储:

  • 类名(m_lpszClassName
  • 基类运行时信息指针(m_pBaseClass
  • 创建对象的函数指针(m_pfnCreateObject
  • 方法调用表(m_methods

通过 CRuntimeClass 可以创建对象、判断继承关系、动态调用方法。

2.2 反射工厂(CRttiFactory)

单例模式管理所有已注册类的 CRuntimeClass 指针。提供:

  • RegisterClass:注册类信息(由宏自动调用)。
  • CreateObject:根据类名查找并创建对象。
  • GetRegisteredClassNames:获取所有类名(调试用)。

2.3 成员函数反射(MethodInvoker)

使用 std::any 统一处理任意类型的参数和返回值。每个成员函数被包装成一个 MethodInvoker 类型的可调用对象:

cpp 复制代码
std::any(CRttiObject*, const std::vector<std::any>&)

内部通过可变参数模板和 std::index_sequence 解析参数列表并调用真实函数。

2.4 宏自动化

借鉴 MFC 的 DECLARE_DYNAMIC / IMPLEMENT_DYNAMIC 宏,自动生成静态 CRuntimeClass 对象和注册代码。成员函数反射也通过宏声明和注册。

三、核心代码解析

3.1 类与结构定义

cpp 复制代码
// 创建对象函数类型
using ObjectConstructor = std::function<CRttiObject*()>;

// 方法调用器类型
using MethodInvoker = std::function<std::any(CRttiObject*, const std::vector<std::any>&)>;

// 方法元信息
class CMethodInfo {
public:
    const char* name;
    MethodInvoker invoker;
    CMethodInfo(const char* n, MethodInvoker i) : name(n), invoker(std::move(i)) {}
};

// 运行时类信息
class CRuntimeClass {
public:
    const char* m_lpszClassName;
    CRuntimeClass* m_pBaseClass;
    ObjectConstructor m_pfnCreateObject;

    CRuntimeClass(const char* className, CRuntimeClass* baseClass, ObjectConstructor ctor);

    CRttiObject* CreateObject() const;
    bool IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

    // 方法管理
    void RegisterMethod(const char* name, MethodInvoker invoker);
    std::any InvokeMethod(CRttiObject* obj, const char* name, const std::vector<std::any>& args = {}) const;
    bool HasMethod(const char* name) const;

private:
    std::unordered_map<std::string, CMethodInfo> m_methods;
};

3.2 反射基类与工厂

cpp 复制代码
// 所有反射类的基类
class CRttiObject {
public:
    virtual ~CRttiObject() = default;
    virtual const CRuntimeClass* GetRuntimeClass() const = 0;
    bool IsKindOf(const CRuntimeClass* pClass) const;
    static CRttiObject* CreateObject(const char* className);
};

// 单例工厂(省略部分代码,详见文末源码)
class CRttiFactory {
    // 线程安全注册与查找
    std::unordered_map<std::string, CRuntimeClass*> m_classMap;
    std::vector<CRuntimeClass*> m_vecRuntimeClasses;
    mutable std::mutex m_mutex;
    // ...
};

3.3 成员函数调用适配器

使用模板将任意成员函数转换为统一签名:

cpp 复制代码
namespace ReflectDetail {
    // 实际调用包装,通过 std::any_cast 提取参数
    template<typename Class, typename Ret, typename... Args, std::size_t... I>
    std::any InvokeImpl(Class* obj, Ret(Class::* func)(Args...), 
                        const std::vector<std::any>& args, std::index_sequence<I...>) {
        if (args.size() != sizeof...(Args))
            throw std::runtime_error("Argument count mismatch");
        if constexpr (std::is_void_v<Ret>) {
            (obj->*func)(std::any_cast<std::decay_t<Args>>(args[I])...);
            return std::any();
        } else {
            return std::any((obj->*func)(std::any_cast<std::decay_t<Args>>(args[I])...));
        }
    }

    // 生成 MethodInvoker
    template<typename Class, typename Ret, typename... Args>
    MethodInvoker MakeInvoker(Ret(Class::* func)(Args...)) {
        return [func](CRttiObject* base, const std::vector<std::any>& args) -> std::any {
            Class* obj = dynamic_cast<Class*>(base);
            if (!obj) throw std::runtime_error("Invalid object type");
            return InvokeImpl(obj, func, args, std::index_sequence_for<Args...>{});
        };
    }
    // 同样提供 const 成员函数版本(略)
}

3.4 宏定义

类反射宏

cpp 复制代码
#define DECLARE_DYNAMIC(className) \
public: \
    static CRuntimeClass class##className; \
    virtual const CRuntimeClass* GetRuntimeClass() const override { return &class##className; } \
    static CRttiObject* CreateObject() { return new className(); }

#define IMPLEMENT_ROOT_DYNAMIC(className) \
    CRuntimeClass className::class##className(#className, nullptr, &className::CreateObject); \
    static struct __Register_##className { \
        __Register_##className() { CRttiFactory::Instance().RegisterClass(&className::class##className); } \
    } __register_##className;

成员函数反射宏(支持0~4个参数)

cpp 复制代码
#define DECLARE_REFLECT_METHOD(className, method) \
    static void _RegisterMethod_##method();

#define IMPLEMENT_REFLECT_METHOD(className, method, ...) \
    /* 通过参数个数选择具体宏,调用 MakeInvoker 并注册 */

宏自动展开后,会在 main 前执行注册代码,确保类和方法在运行时可用。

四、使用示例

4.1 定义反射类

TestReflect.h

cpp 复制代码
#pragma once
#include "ReflectClass.h"

class CTestReflect : public CRttiObject
{
    DECLARE_DYNAMIC(CTestReflect)
    DECLARE_REFLECT_METHOD(CTestReflect, SetValue)
    DECLARE_REFLECT_METHOD(CTestReflect, GetValue)

public:
    CTestReflect() : m_nValue(2) {}
    virtual ~CTestReflect() {}

    void SetValue(int nValue, float fValue) { m_nValue = nValue; m_fValue = fValue; }
    int GetValue() { return m_nValue; }

private:
    int m_nValue;
    float m_fValue;
};

4.2 实现注册

TestReflect.cpp

cpp 复制代码
#include "TestReflect.h"

IMPLEMENT_ROOT_DYNAMIC(CTestReflect)
IMPLEMENT_REFLECT_METHOD(CTestReflect, SetValue, int, float)
IMPLEMENT_REFLECT_METHOD(CTestReflect, GetValue)

4.3 动态调用

Main.cpp

cpp 复制代码
#include "TestReflect.h"
#include <iostream>

int main()
{
    CRttiObject* obj = CRttiObject::CreateObject("CTestReflect");
    if (obj) {
        const CRuntimeClass* cls = obj->GetRuntimeClass();

        // 带参数调用
        std::vector<std::any> args = { 10, 2.5f };
        cls->InvokeMethod(obj, "SetValue", args);

        // 带返回值调用
        std::any ret = cls->InvokeMethod(obj, "GetValue", {});
        if (ret.has_value()) {
            int val = std::any_cast<int>(ret);
            std::cout << "GetValue: " << val << std::endl; // 输出 10
        }
        delete obj;
    }
    return 0;
}

五、关键技术细节与注意事项

5.1 静态初始化顺序

  • 使用局部静态单例(CRttiFactory::Instance())确保工厂在首次访问时构造。
  • 宏生成的注册类在 main 前执行,但调用 RegisterClass 时工厂已构造,顺序安全。

5.2 类型严格匹配

std::any_cast 要求类型完全一致 ,包括 cv 限定符和引用。示例中 2.5f 显式指定为 float,若误写 2.5double)将抛出 std::bad_any_cast

5.3 函数重载处理

若存在同名重载函数,需通过 static_cast 指定确切签名,宏已包含此转换。

5.4 线程安全

工厂的 RegisterClass 和查询方法均加锁,可在多线程环境下安全使用。

5.5 扩展性

  • 支持更多参数:参照现有宏模式增加 IMPLEMENT_REFLECT_METHOD_5 等即可。
  • 支持非 void 返回值:已有完整支持,无需额外处理。

六、完整代码获取

完整头文件 ReflectClass.h 可进q裙提供。项目结构如下:

复制代码
├── ReflectClass.h       # 反射核心实现
├── TestReflect.h/cpp    # 示例反射类
└── Main.cpp             # 测试入口

七、总结

本文实现了一套仿 MFC 的 C++ 反射系统,具备以下亮点:

  • 轻量级:仅依赖 C++17 标准库,无第三方依赖。
  • 功能全面:支持动态创建、继承判断、成员函数调用。
  • 类型安全 :利用 std::any 和模板推导,编译期保证类型正确。
  • 易用性:宏自动化注册,对业务代码侵入极小。

这套反射机制可应用于插件系统、序列化/反序列化、脚本绑定、RPC 调用等场景,为 C++ 项目带来动态语言的灵活性。

相关推荐
Elastic 中国社区官方博客2 小时前
在 Elastic 中使用 OpenTelemetry 内容包可视化 OpenTelemetry 数据
大数据·开发语言·数据库·elasticsearch·搜索引擎
C+++Python2 小时前
如何学习Python的应用领域知识?
开发语言·python·学习
疯狂打码的少年2 小时前
【Day12 Java转Python】Python工程的“骨架”——模块、包与__name__
java·开发语言·python
全栈开发圈3 小时前
新书速览|MATLAB数据分析与可视化实践:视频教学版
开发语言·matlab·数据分析
网域小星球3 小时前
C 语言从 0 入门(二十二)|内存四区:栈、堆、全局、常量区深度解析
c语言·开发语言
晓纪同学3 小时前
EffctiveC++_第三章_资源管理
开发语言·c++·算法
蚊子码农3 小时前
每日一题--C语言指针与内存泄漏:一道小问题的深度复盘
c语言·开发语言
Fanfanaas3 小时前
Linux 系统编程 进程篇(一)
linux·运维·服务器·c语言·开发语言·网络·学习
星辰徐哥3 小时前
ARP缓存表:作用、查看方法与刷新技巧
开发语言·缓存·php