跟我学c++高级篇——反射的基本实现方式

文章介绍了C++中如何通过宏、模板和元编程等技术实现反射功能,虽然C++原生不支持反射,但随着标准的更新,反射技术逐渐得到支持。文中列举了获取类名、参数数量、类型和地址的方法,并提及模板和元编程在反射中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、c++中的反射基本实现

综合反射在各种高级语言中的应用以及前面分析过的反射的原理,就可以明白,一种概念或者说技术在某种语言中是否拥有,更准确的其实是说是否原生拥有。毕竟,没有可以通过某种技术或者手段模拟出来,理论上说,只要达到最后的效果相同,就可以认为支持这项技术。反射亦是如此!
c++原生可以说基本是不支持反射的,但是通过一些技术手段可以实现,特别是随着c++标准的迭代,包括反射的一些技术增加到了标准内,这就意味着,c++原生支持反射的可能性会越来越大,但能支持到何种程度,就是另外一种问题了,做标准可不比开发程序,要严谨很多,不然也不会有不少的技术被标准委员会反复推延。

二、反射实现的方法

在c++中,基本的静态反射,可以通过宏和模板来实现,当然二者结合也可以实现。这里还是强调一下,单纯的为了反射的目的的而模拟实现反射效果的暂时不在此讨论之列。在宏和模板实现反射的机制下,可能会利用到萃取和特化。特别是前面提到的一些SFINA和元编程等中的技术(变参模板和变参宏等)。
正如前面分析提到,要想实现反射,首先知道类的信息,这些信息包括大小,名称,变量数量、地址,函数的地址等等。换句话说,需要通过宏和模板等技术只要能得到这些信息,就基本可以实现反射了。不过,在实现的过程中,如何更简单高效的完成工作和达到目的,这就需要八仙过海的本事了。

三、例程

上面提到了,要想实现反射,必须能得名称、属性(类内部变量定义)、地址等,那么就需要先处理最常见的,拿到名称和处理数量

1、得到名称
这个比较简单毕竟类的名称都是可以各种方法得到的,而且在反射的过程中一般也会给出类的名称。而且用宏来处理这种事情极其简单,字符串的替换即可。当然,c++中也为基础的类型提供了typeid可以得到名称。

2、得到数量
这个可以用模板和宏,下面是宏的方式:

//注意:VC编译器把宏可变参当成一个整体进行预处理展开,可能结果有问题,可启用/Zc:preprocessor编译选项,或引入ZH_EXPAND(...) VA_ARGS 展开,建议在Gcc下展开。
#define GET_NTH_ARG(  _1,  _2,  _3,  _4,  _5,  _6,  _7,  _8,  _9,  _10, _11, _12, _13, _14, _15, _16, n, ...) n
#define GET_ARG_COUNT(...) GET_NTH_ARG (__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9,  8,  7,  6,  5,  4,  3,  2,  1)
int main()
{
    GET_ARG_COUNT(a,b,3,d);
}

这个代码的参数可以自己按照需要进行扩展,写成1~N,当然N不可能太大,毕竟实际情况有十个参数的都是多的了。不过这个不支持0个的参数的。
下面是模板的方式:

#define TEM_GET_PARAM_SIZE_BY_CONSTEXPR (...) TempGetParmCons(__VA_ARGS__)
#define TEM_GET_PARAM_SIZE_BY_SIZEOF(...) (sizeof(TemGetParmBySize)(__VA_ARGS__))/sizeof(char) -1
#define TEM_GET_TYPE_SIZE(...) TemGetTypeSize<__VA_ARGS__>::value

template<typename ...T>
constexpr size_t TempGetParmCons(T ...params)
{
    return sizeof ... (T);
}
template<typename ...T>
auto TemGetParmBySize(T&& ...param)->char(&)[sizeof ...(T) + 1];

template<typename ...T>
struct TemGetTypeSize
{
    static constexpr size_t value = sizeof ... (T);
};

这个技术就更简单了,只是简单的使用了sizeof…的各种情况。

3、得到类型和地址
通过宏还可以得到很多的东西,比如利用#,##进行字符串的处理和连接。还可以以去除、增加小括号(也可以同时处理括号内容),这样就可以在宏反射过程中得到具体的类型和变量以及它们的名称。

#define TYPE_NAME_ALLDEL(...)
#define TYPE_NAME_DELP(...) __VA_ARGS__
#define TYPE_NAME_GET(n) TYPE_NAME_DELP n

#define CLASSMEM_OFFSET_TYPE(m,...)(reinterpret_cast<size_t>(&reinterpret_cast<char const volatile&>((((__VA_ARGS__*)0)->m))))

4、模板处理表达式展开和元数据处理
在前面的分析提到过,可以用折叠表达式来完成自动的展开,而这种展开就可以通过模板来实现对反射过程中的序列化:

template<typename T>
struct Serializer
{
    using mSequence = std::make_index_sequence<T::mSize>;

    template<size_t ...M>
    static void WriteDataStream(std::index_sequence<M...>, const T& cv)
    {
        (T::template SeqMeta<M>::WriteData(cv), ...);
    }

    static void WriteData(const  T& cv)
    {
          WriteDataStream(mSequence(),cv);
    }
};

当然,萃取和SFINAE等技术都可以用到其中,就看对开发的需求了。另外一些新的c++标准中的技术也可以应用,比如Concepts等都可以很好解决一些问题。当然,typeid,sizeof…,decltype等就更不用提了。
5、模板技术和元编程
元编程就不用说了,模板技术也可以广泛的应用到反射中来,比如模板的偏特化等等,这些在前面都有不少的例子,这是就不再展开了。

本来想搞一个简单的应用做个例子,发现还真是有点难度(几十行代码真不好写出来)。还是后面再专门写一篇相关的文章为好。反射确实是比较不好写的容易懂,毕竟当初光宏编程就惹得一众声讨,更别说再加模板和元编程,这个得慢慢来。另外,反射本身在新的c++标准中可能会不断的完善,分析讲解的一些技术可能过几年就不会在反射中应用了,这个还是需要看标准的发展和应用的普及程度。

四、总结

太极生两仪,两仪有四象,四象生八卦。正如道家所说一生二,二生三,三生万物。学习也要从简单抓起,然后反复不停的在此基础上锤练,不断的向上发展,让整个体系愈发的完整并且向着新的未知不断的探索。对个人来说,技术的精湛莫不如是。对技术本身来说,更是如此。
要站在巨人的肩膀上,不断的向前看,看向更高。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值