
到现在为止,C++ 仍然是计算机编程领域的经典语言之一,C++ 17 标准在2017上半年已经探讨确定。本期我们汇集了编程专家——祁宇(《深入应用 C++ 11》作者,C++ 开源社区 purecpp.org 创始人)的多年心得小结,并具体介绍了 C++ 17 最新标准中值得开发者关注的新特点和基本用法。
文/祁宇
本文将借助分析 magic _ get 源码来介绍 magic _ get 实现的关键科技,深入剖析实现 pod 类型反射的机理。
反射是一种根据元数据来获得类外部信息的模式,通过元数据就可以获得对象的字段和技巧等信息。C# 和 Java 的反射模式都是通过获得对象的元数据来推动的。反射可以用于依赖注入、ORM 对象-实体映射、序列化和反序列化等与对象原本信息密切相关的领域。比如 Java 的 Spring 框架,其依赖注入的基础是构建在反射的基础之上的,可以按照元数据获取类型的信息并动态建立对象。ORM 对象-实体之间的映射也是借助反射实现的。Java 和 C# 都是基于前面运行时的语言,中间运行时提供了反射模式,所以反射针对运行时语言来说很容易,但是针对没有中间运行时的语言,要想实现反射是很困难的。
在2016年的 CppCon 技术大会上,Antony Polukhin 做了一个关于 C++ 反射的发言vc6.0怎么写c语言,他强调了一个实现反射的新思路,即无需使用宏、标记和额外的软件就能推动反射。看起来似乎是一件不也许完成的任务,因为 C++ 是没有反射模式的,无法直接获得对象的元信息。但是 Antony Polukhin 发现对 pod 类型使用 Modern C++ 的模板元方法可以实现这种的编译期反射。他开源了一个 pod 类型的编译期反射库 magic _ get(),这个库也打算开启 boost。我们来看看 magic _ get 的使用示例。
#include <boost pfr="" core.hpp="">struct foo { int some_integer; char c;};foo f {777, '!'};auto& r1 = boost::pfr::flat_get<0>(f); //通过索引来访问对象foo的第1个字段auto& r2 = boost::pfr::flat_get<1>(f); //通过索引来访问对象foo的第2个字段</boost>
通过这个例子可以发现,magic _ get 确实实现了非侵入式访问 foo 对象的数组,不需要写任何宏、额外的代码或者专门的软件,直接在编译期就可以访问 pod 对象的数组,没有运行期负担,确实有点 magic。
本文将借助分析 magic _ get 源码来介绍 magic _ get 实现的关键科技,深入剖析实现 pod 类型反射的机理。
实现 pod 类型反射的模式是这么的:先将 pod 类型转化为对应的 tuple 类型,接下来将 pod 类型的值赋给 tuple,然后就可以通过索引去访问 tuple 中的元素了。所以推动 pod 反射的关键就是如何将 pod 类型转化为对应的 tuple 类型和 pod 值数组给 tuple。
pod 类型对应的 tuple 类型是什么样的呢?以下面的 foo 为例,foo 对应的 tuple 应该是 tuple<int, char>,即 tuple 中的元素种类和排序和 pod 类型中的数组完全一一对应。
根据结构体生成一个 tuple 的基本模式是,按次序将结构体中每个数组的类别萃取出来并储存起来,后面再取下来生成对应的 tuple 类型。然而字段的类别是不同的,C++ 也没有一个能直接保存不同类型的容器vc6.0怎么写c语言,因此必须一个变通的方式,用一个间接的方式来储存萃取出来的字段类别,即将类型转化为一个 size _ t 类型的 id,将这个 id 保存到一个 array<size_t, N> 中,后面根据这个 id 来获得实际的 type 并生成对应的 tuple 类型。
这里应该解决的一个问题是怎样推动类型和 id 的互相转换。

先通过一个空的模板类用来存放实际的类别,再通过 C++ 14 的 constexpr 特性,在编译期返回某个类别对应的编译期 id,就可以实现 type 转换为 id 了。具体代码如下:
http://ipad-cms.csdn.net/cms/article/code/3445
上面的代码在编译期将类别 int 和 char 做了一个编码,将类别转换为一个具体的编译期常量,后面就可以按照这种编译期常量来获得对应的详细类型。
编译期根据 id 获取 type 的代码如下:
constexpr auto id_to_type( std::integral_constant<std::size_t, 6=""> ) noexcept { int res{}; return res; }constexpr auto id_to_type( std::integral_constant<std::size_t, 9=""> ) noexcept { char res{}; return res; }</std::size_t,></std::size_t,>
上面的代码中 id _ to _ type 返回的是 id 对应的类别的示例,如果要获得 id 对应的类别还必须借助 decltype 推导出来。magic _ get 通过一个宏将 pod 基本类别都做了一个编码,以推动 type 和 id 在编译期的互相转换。
#define REGISTER_TYPE(Type, Index) \ constexpr std::size_t type_to_id(identity<type>) noexcept { return Index; } \ constexpr auto id_to_type( std::integral_constant<std::size_t, index=""> ) noexcept { Type res{}; return res; } \// Register all base types here REGISTER_TYPE(unsigned short , 1) REGISTER_TYPE(unsigned int , 2) REGISTER_TYPE(unsigned long long , 3) REGISTER_TYPE(signed char , 4) REGISTER_TYPE(short , 5) REGISTER_TYPE(int , 6) REGISTER_TYPE(long long , 7) REGISTER_TYPE(unsigned char , 8) REGISTER_TYPE(char , 9) REGISTER_TYPE(wchar_t , 10) REGISTER_TYPE(long , 11) REGISTER_TYPE(unsigned long , 12) REGISTER_TYPE(void* , 13) REGISTER_TYPE(const void* , 14) REGISTER_TYPE(char16_t , 15) REGISTER_TYPE(char32_t , 16) REGISTER_TYPE(float , 17) REGISTER_TYPE(double , 18) REGISTER_TYPE(long double , 19)</std::size_t,></type>
将类别编码以后,保存在那里或者怎么取下来是接着要缓解的难题。magic _ get 通过定义一个 array 来保存结构体字段类别 id。
template <class t,="" std::size_t="" n=""> struct array { typedef T type; T data[N]; static constexpr std::size_t size() noexcept { return N; } };</class>
array 中的定长数组 data 中保存数组类型对应的 id,数组下标就是字段在结构体中的位置索引。
前面介绍了怎样实现字段类别的保存和获得,那么这个字段类别是怎样从 pod 结构体中萃取出来的呢?具体的做法分为三步:
下面是详细实现代码:

template <class t="">constexpr auto fields_count_and_type_ids_with_zeros() noexcept { static_assert(std::is_trivial<t>::value, "Not applyable"); array<std::size_t, sizeof(t)=""> types{}; detect_fields_count_and_type_ids<t>(types.data, std::make_index_sequence<sizeof(t)>{}); return types;}template <class t="">constexpr auto array_of_type_ids() noexcept { constexpr auto types = fields_count_and_type_ids_with_zeros<t>(); constexpr std::size_t count = count_nonzeros(types); array<std::size_t, count=""> res{}; for (std::size_t i = 0; i < count; ++i) { res.data[i] = types.data[i]; } return res; }</std::size_t,></t></class></sizeof(t)></t></std::size_t,></t></class>
定义 array 时必须定义一个固定的变量长度,长度为多少适合呢?应按结构体最多的字段数来确认。因为结构体的数组数最多为 sizeof(T),所以 array 的长度设置为 sizeof(T)。array 中的元素全部初始化为0。一般情况下,结构体字段数通常不会超过 array 的厚度,那么 array 中就经常出现多余的元素,所以还必须将 array 中多余的数组移除,只储存有效的泛型类别 id。具体的做法是计算出 array 中非零的元素有多少,接着再把非零的元素赋给一个新的 array。下面是推导 array 非零元素个数,同样是通过 constexpr 实现编译期计算。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-129594-1.html
在北上广都没问题