前置知识: C++

C++反射与元编程

3 minAdvanced2026/6/14

编译期反射与代码生成

概述

反射(Reflection)是指在运行时或编译期获取型的结构信息(如成员变量、成员函数、基等)的能力。C++ 长期以来缺乏语言级别的反射支持,开发者需要依赖宏、模板元编程等技巧实现有限的反射功能。C++26 有望引入静态反射提案(P2996),届时将提供标准化的编译期反射机制。

元编程(Metaprogramming)是通过程序来生成或变换程序的编程范式。C++ 的模板系统是一种灵完备的元编程语言,可以在编译期执行计算、生成代码和进行型操作。模板元编程与反射密切相关:反射获取型信息,元编程基于这些信息生成代码。

基础概念

静态反射与动态反射

  • 静态反射:在编译期获取型信息,零运行时开销,C++26 反射提案属于此
  • 动态反射:在运行时获取型信息,需要型注册和运行时数据结构,Java/C# 风格

C++ 选择了静态反射路线,与零开销原则一致。编译期反射的结果可以在编译期使用,驱动代码生成。

元编程的主要手段

手段说明
模板特化根据型选择不同实现
SFINAE替换失败不是错误,用于条件编译
constexpr if编译期条件分支(C++17)
概念(Concepts)约束模板参数(C++20)
编译期反射获取型结构信息(C++26 提案)

快速上手

编译期型信息

#include <typeinfo>
#include <iostream>

// 使用 typeid 获取类型名称(运行时,依赖编译器实现)
std::cout << typeid(int).name() << std::endl;
std::cout << typeid(double).name() << std::endl;

// 使用 type_traits 获取编译期类型信息
#include <type_traits>

static_assert(std::is_integral_v<int>);          // true
static_assert(std::is_pointer_v<int*>);           // true
static_assert(std::is_base_of_v<Base, Derived>);  // 继承关系

编译期计算

#include <type_traits>

// 编译期计算斐波那契数列
template<int N>
struct Fibonacci {
    static constexpr int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};

template<>
struct Fibonacci<0> {
    static constexpr int value = 0;
};

template<>
struct Fibonacci<1> {
    static constexpr int value = 1;
};

// 使用 constexpr 函数更简洁
constexpr int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}

static_assert(fib(10) == 55);

详细用法

结构化字段遍历(聚合反射

#include <tuple>
#include <string>

// 通过结构化绑定遍历聚合类型的字段
struct Person {
    std::string name;
    int age;
    double height;
};

// 将聚合类型转为 tuple 引用
template<typename T>
auto toTupleRef(T& obj) {
    // 针对具体类型手动映射
    if constexpr (std::is_same_v<T, Person>) {
        return std::tie(obj.name, obj.age, obj.height);
    }
}

// 通用的字段遍历
template<typename T, typename Func>
void forEachField(T& obj, Func&& func) {
    auto t = toTupleRef(obj);
    std::apply([&func](auto&... fields) {
        (func(fields), ...);  // 折叠表达式遍历每个字段
    }, t);
}

// 使用
Person p{"张三", 25, 175.5};
forEachField(p, [](auto& field) {
    std::cout << field << " ";
});
// 输出: 张三 25 175.5

编译期字符串处理

#include <array>

// 编译期字符串(C++20 方式)
template<size_t N>
struct ConstexprString {
    std::array<char, N> data{};

    constexpr ConstexprString(const char (&str)[N]) {
        for (size_t i = 0; i < N; ++i) {
            data[i] = str[i];
        }
    }

    constexpr size_t size() const { return N - 1; }

    constexpr bool operator==(const ConstexprString& other) const {
        return data == other.data;
    }
};

// 编译期字符串比较
constexpr ConstexprString hello{"hello"};
constexpr ConstexprString world{"world"};
static_assert(hello.size() == 5);
static_assert(!(hello == world));

型注册表

#include <unordered_map>
#include <functional>
#include <string>
#include <any>

// 简易类型注册系统
class TypeRegistry {
    std::unordered_map<std::string, std::function<std::any()>> creators_;

public:
    template<typename T>
    void registerType(const std::string& name) {
        creators_[name] = []() -> std::any {
            return T{};
        };
    }

    std::any create(const std::string& name) const {
        auto it = creators_.find(name);
        if (it != creators_.end()) {
            return it->second();
        }
        return {};
    }

    bool hasType(const std::string& name) const {
        return creators_.count(name) > 0;
    }
};

// 使用
TypeRegistry registry;
registry.registerType<int>("int");
registry.registerType<std::string>("string");

auto val = registry.create("int");
if (val.has_value()) {
    std::cout << std::any_cast<int>(val) << std::endl;  // 0
}

常见场景

序列化框架

#include <vector>
#include <string>
#include <sstream>

// 字段描述器
struct FieldInfo {
    std::string name;
    std::function<std::string(const void*)> getter;
    std::function<void(void*, const std::string&)> setter;
};

// 类描述器
class ClassDescriptor {
    std::vector<FieldInfo> fields_;
public:
    void addField(FieldInfo info) { fields_.push_back(std::move(info)); }
    const auto& fields() const { return fields_; }
};

// 为 Person 注册字段
ClassDescriptor describePerson() {
    ClassDescriptor desc;
    desc.addField({"name",
        [](const void* obj) { return static_cast<const Person*>(obj)->name; },
        [](void* obj, const std::string& v) { static_cast<Person*>(obj)->name = v; }
    });
    desc.addField({"age",
        [](const void* obj) { return std::to_string(static_cast<const Person*>(obj)->age); },
        [](void* obj, const std::string& v) { static_cast<Person*>(obj)->age = std::stoi(v); }
    });
    return desc;
}

依赖注入容器

#include <memory>
#include <unordered_map>
#include <typeindex>
#include <functional>

class ServiceContainer {
    std::unordered_map<std::type_index, std::function<std::shared_ptr<void>()>> factories_;

public:
    template<typename Interface, typename Implementation>
    void registerService() {
        factories_[std::type_index(typeid(Interface))] = []() {
            return std::make_shared<Implementation>();
        };
    }

    template<typename Interface>
    std::shared_ptr<Interface> resolve() {
        auto it = factories_.find(std::type_index(typeid(Interface)));
        if (it != factories_.end()) {
            return std::static_pointer_cast<Interface>(it->second());
        }
        return nullptr;
    }
};

注意事项

  • C++26 反射提案仍在标准化过程中,不同编译器的实验性支持可能不同,生产代码暂不建议依赖
  • 当前的反射技巧(如宏注册、聚合绑定)都有局限性,无法自动发现型的所有成员
  • 模板元编程的编译错误信息通常很长且难以理解,建议使用 static_assert 和概念提供清晰的错误提示
  • 过度使用元编程会显著增加编译时间,应权衡编译期计算与运行时计算的取舍
  • 编译期反射的代码调试困难,建议在运行时测试逻辑正确性后再转为编译期实现

进阶用法

C++26 反射提案预览

// P2996 提案语法(尚未正式标准化)
// 获取类型的所有成员
// consteval auto members = std::meta::members_of(^MyStruct);

// 遍历成员并生成代码
// template<typename T>
// consteval auto generateSerializer() {
//     std::string code;
//     for (auto member : std::meta::members_of(^T)) {
//         code += "serialize(obj." + std::meta::name_of(member) + ");\n";
//     }
//     return code;
// }

// 使用 meta::info 进行编译期类型操作
// static_assert(std::meta::is_class(^MyStruct));
// static_assert(std::meta::is_public(^MyStruct::x));

基于宏的简易反射

#include <vector>
#include <string>
#include <tuple>

// 反射宏:注册字段名和偏移量
#define REFLECT_FIELDS(...) \
    static auto fields() { \
        return std::make_tuple(__VA_ARGS__); \
    } \
    static auto fieldNames() { \
        return std::vector<std::string>{MACRO_TO_STRING(__VA_ARGS__)}; \
    }

#define MACRO_TO_STRING(...) #__VA_ARGS__

// 使用宏的类定义
struct Student {
    std::string name;
    int age;
    double score;

    REFLECT_FIELDS(name, age, score)
};

// 基于反射的通用打印函数
template<typename T>
void printObject(const T& obj) {
    auto names = T::fieldNames();
    auto values = obj.fields();
    size_t i = 0;
    std::apply([&](const auto&... vals) {
        ((std::cout << names[i++] << ": " << vals << "\n"), ...);
    }, values);
}

编译期代码生成

#include <array>

// 编译期生成查找表
template<typename T, T (*Func)(T), size_t N>
struct LookupTable {
    std::array<T, N> table{};

    constexpr LookupTable() {
        for (size_t i = 0; i < N; ++i) {
            table[i] = Func(static_cast<T>(i));
        }
    }

    constexpr T operator[](size_t i) const { return table[i]; }
};

// 编译期计算正弦值
constexpr float sinDeg(int deg) {
    // 简化实现,实际应使用泰勒展开
    return static_cast<float>(deg);  // 占位
}

// 生成 0-359 度的正弦查找表
constexpr auto sinTable = LookupTable<float, sinDeg, 360>{};

// 运行时直接查表,零计算开销
float value = sinTable[90];