C++模板元编程详细教程(之九)
前序文章请看:
C++模板元编程详细教程(之一)
C++模板元编程详细教程(之二)
C++模板元编程详细教程(之三)
C++模板元编程详细教程(之四)
C++模板元编程详细教程(之五)
C++模板元编程详细教程(之六)
C++模板元编程详细教程(之七)
C++模板元编程详细教程(之八)
多选一结构
这一章我们来看看如何编写一个多选一的结构,STL中提供了std::variant,我们就来实现一个简易版。(注意下面我们实现的variant与STL中的是有区别的,但大体思路是相同的,请读者不要以本节的代码参考使用std::variant)。
我们的诉求是,创建一个数据结构,「可能」存放多种类型,但同一时间只能有一种,并且可以在运行期变化为另一种。这里比较容易想到的做法就是用共合体类型存储数据,再加一个用于表示当前哪种数据是生效的index。请看代码:
template <typename T1, typename T2>
class variant {
public:// 针对每种情况的构造variant(const T1 &t1);variant(const T2 &t2);
private:union {T1 t1;T2 t2;} data;int index; // 当前生效的数据序号
};
只不过这样我们很快就会发现很多严重的问题:
- 对于任意个数的参数,无法映射到
union结构中; - 如果数据类型不含无参构造函数,
union结构的构造会报错; variant的构造函数依赖数据类型的拷贝构造,对于不可拷贝类型来说无法创建。
因此,我们必须换一个思路。既然一开始我们想到用union,主要也是为了内存空间的复用,所以我们只需要自己来维护一片数据空间就好了,以参数类型中长度最大的为准,创建一个缓存空间即可。
template <typename... Types>
class variant {private:void *data = std::malloc(std::max(sizeof(Types)...)); // 计算出最长的Typeint index; // 当前生效的数据序号
};
接下来的任务就是,构造函数。由于这里的Types是变参,所以无法穷举,与此同时,我们希望可以就地构造,跳过「拷贝构造」这个阶段,以支持不可拷贝类型的数据,因此必须有一个Index来标识,后面跟变参:
template <typename... Types>
class variant {public:template <size_t Index, typename... Args>variant(Args &&... args); // 大概是这个意思private: void *data = std::malloc(std::max(sizeof(Types)...)); // 计算出最长的Typeint index; // 当前生效的数据序号
};
然而,出于语法限制,构造函数用模板生成的话,是无法手动实例化的,例如:
variant<1, int> va; // <1, int>会识别为类型的模板参数,而不是构造函数的模板参数
因此,构造函数的模板参数只能依赖于自动推导,所以,我们就需要提供一个工具,用来「传递」这个Index。
template <size_t Index>
struct in_place_index_t {}; // 单纯的静态工具,用于传递Index,没有运行期意义template <size_t Index>
constexpr inline in_place_index_t<Index> in_place_index; // 对应in_place_index_t类型的实例template <typename... Types>
class variant {public:template <size_t Index, typename... Args>variant(const in_place_index_t<Index> &, Args &&... args);private:void *data = std::malloc(std::max(sizeof(Types)...)); int index;
};
然后,构造时,也同样通过in_place_index来构造,传递这个Index:
// 用于测试的类型
struct Test1 {Test1(int, double);
};
struct Test2 {Test1(char, int);
};void Demo() {variant<Test1, Test2> var{in_place_index<0>, 1, 1.5}; // 用于构造Test1类型variant<Test1, Test2> var{in_place_index<1>, 'A', 1}; // 用于构造Test2类型
}
那么接下来的问题就是如何解析了,variant本身的参数是一组typename列表,但构造传进来的是一个Index序号,如何对应呢?相信读者到现在应该已经建立初步的感觉了,没错,递归大法好!
template <size_t Index, typename Head, typename... Args>
struct get_type_by_index : get_type_by_index<Index - 1, Args...> {};template <typename Head, typename... Args>
struct get_type_by_index<0, Head, Args...> {using type = Head;
};// 验证Demo
void Demo() {std::cout << std::is_same_v<typename get_type_by_index<2, int, double, char, void *>::type , char> << std::endl; // 1
}
有了这个工具,我们就可以从类型列表里通过Index取出对应的类型了,以此来完成variant的构造函数:
// 实现构造函数
template <typename... Types>
template <size_t Index, typename... Args>
variant<Types...>::variant(const in_place_index_t<Index> &, Args &&... args): index(Index) {// 先把需要构造的类型拿出来using data_type = typename get_type_by_index<Index, Args...>::type;// 在data处进行就地构造new(data) std::decay_t<data_type>(std::forward<Args>(args)...);
}
最难的构造已经完成了,我们先实现一下周边功能,析构、拷贝构造和赋值函数先放一放,给出一个阶段性的代码:
template <size_t Index>
struct in_place_index_t {}; // 单纯的静态工具,用于传递Index,没有运行期意义template <size_t Index>
constexpr inline in_place_index_t<Index> in_place_index; // 对应in_place_index_t类型的实例template <size_t Index, typename Head, typename... Args>
struct get_type_by_index : get_type_by_index<Index - 1, Args...> {};template <typename Head, typename... Args>
struct get_type_by_index<0, Head, Args...> {using type = Head;
};template <typename... Types>
class variant {public:template <size_t Index, typename... Args>variant(const in_place_index_t<Index> &, Args &&... args);// 获取当前序号int index() const;// 取出数据template <size_t Index>auto get() const -> std::add_lvalue_reference_t<std::decay_t<typename get_type_by_index<Index, Types...>::type>>;private:void *data_ = std::malloc(std::max(sizeof(Types)...)); // 计算出最长的Typeint index_; // 当前生效的数据序号
};// 实现构造函数
template <typename... Types>
template <size_t Index, typename... Args>
variant<Types...>::variant(const in_place_index_t<Index> &, Args &&... args): index_(Index) {// 先把需要构造的类型拿出来using data_type = typename get_type_by_index<Index, Args...>::type;// 在data处进行就地构造new(data_) std::decay_t<data_type>(std::forward<Args>(args)...);
}// 获取序号
template <typename... Types>
int variant<Types...>::index() const {return index_;
}// 取出数据(这个功能在std::variant中其实实现在std::get中)
template <typename... Types>
template <size_t Index>
auto variant<Types...>::get() const -> std::add_lvalue_reference_t<std::decay_t<typename get_type_by_index<Index, Types...>::type>> {using data_type = std::decay_t<typename get_type_by_index<Index, Types...>::type>;return *static_cast<data_type *>(data_);
}
接下来我们要攻克的,就是析构、拷贝构造、拷贝赋值这几个问题了,它们的难点在于,没有静态的Index参数可供使用,但还要找到现在数据的类型。就拿析构来说,析构函数并没有静态的Index参数,只有一个运行时的index成员,但我们要找到此时index_成员表示的data_所指向数据的实际类型,然后调用这个类型的析构函数。拷贝构造、拷贝赋值也同样需要先析构现有数据,所以面临同样的问题。
这种情况怎么办呢?类比前面章节介绍的元组的动态get方法,我们这里也采取「静态穷举生成所有可能情况代码」的方法。
以析构为例,在析构时,要根据index_去判断当前保存的数据是哪一种类型的,然后去调用对应类型的析构函数。因此,我们需要在静态期就提供每一种类型的析构方法,然后将它们和类型的Index关联起来,这样在运行时就可以调用了。
template <typename... Types>
class variant {public:// 无关代码暂时省略 ~variant(); // 析构函数private:void *data_ = std::malloc(std::max(sizeof(Types)...));int index_; // 当前生效的数据序号// 生成每一个Type下对应类型的析构方法template <typename Type>void destory_data();
};template <typename... Types>
template <typename Type>
void variant<Types...>::destory_data() {// 用data_指针按照对应的类型调用析构函数static_cast<std::add_pointer_t<Type>>(data_)->~Type();
}// 实现析构函数
template <typename... Types>
variant<Types...>::~variant() {// 析构函数中,把所有的index对应的destory_data保存下来(编译期完成这样一个映射表)std::array<void (variant<Types...>::*)(), sizeof...(Types)> destory_functions {&variant<Types...>::destory_data<Types>... // 按照类型参数展开,把对应的destory_data实例的函数指针保存下来};// 到了运行期,根据当前实际的index_值,选择对应的析构方法if (data_ != nullptr) {(this->*destory_functions.at(index_))();std::free(data_);}
}
对于拷贝构造、拷贝赋值来说,首先把原来的内容析构调,然后再根据被复制方的index_,来调用对应的构造函数。于是,我们还需要补充一个index_与构造函数的映射表,思路和析构完全相同:
template <typename... Types>
class variant {public:// 无关代码先省略variant(const variant &va);private:void *data_ = std::malloc(std::max(sizeof(Types)...));int index_; // 当前生效的数据序号// 生成每一个Type下对应类型的析构方法template <typename Type>void destory_data();// 生成每一个Type下对应类型的构造方法// 注意,由于我们要把生成的所有方法保存在数组中,因此函数类型需要一致,所以参数用泛型指针template <typename Type>void create_data(const void *obj);
};template <typename... Types>
template <typename Type>
void variant<Types...>::create_data(const void *obj) {// 用data_指针按照对应的类型调用拷贝构造new(data_) Type(*static_cast<const Type *>(obj));
}template <typename... Types>
variant<Types...>::variant(const variant &va): index_(va.index_) {// 静态期保存所有类型的构造函数std::array<void (variant<Types...>::*)(const void *), sizeof...(Types)> create_functions {&variant<Types...>::create_data<Types>...};// 动态时根据index_调用对应的构造(this->*create_functions.at(index_))(va.data_);
}
有了这样的思路,那么我们就可以完善所有的代码了,拷贝赋值函数可以按照相同的方法写出来了(只不过需要先析构,再重新构造)。下面给出整个项目的完整代码:
// 辅助工具
template <size_t Index>
struct in_place_index_t {}; // 单纯的静态工具,用于传递Index,没有运行期意义template <size_t Index>
constexpr inline in_place_index_t<Index> in_place_index; // 对应in_place_index_t类型的实例template <size_t Index, typename Head, typename... Args>
struct get_type_by_index : get_type_by_index<Index - 1, Args...> {};template <typename Head, typename... Args>
struct get_type_by_index<0, Head, Args...> {using type = Head;
};// variant结构的声明
template <typename... Types>
class variant {public:template <size_t Index, typename... Args>variant(const in_place_index_t<Index> &, Args &&... args);variant(const variant &va);variant(variant &&va);~variant();variant &operator =(const variant &va);variant &operator =(variant &&va);// 获取当前序号int index() const;// 取出数据template <size_t Index>auto get() const -> std::add_lvalue_reference_t<std::decay_t<typename get_type_by_index<Index, Types...>::type>>;private:void *data_ = std::malloc(std::max(sizeof(Types)...));int index_; // 当前生效的数据序号// 生成每一个Type下对应类型的析构方法template <typename Type>void destory_data();// 生成每一个Type下对应类型的构造方法template <typename Type>void create_data(const void *obj);
};// 实现构造函数
template <typename... Types>
template <size_t Index, typename... Args>
variant<Types...>::variant(const in_place_index_t<Index> &, Args &&... args): index_(Index) {using data_type = typename get_type_by_index<Index, Args...>::type;new(data_) std::decay_t<data_type>(std::forward<Args>(args)...);
}// 实现析构函数
template <typename... Types>
variant<Types...>::~variant() {// 析构函数中,把所有的index对应的destory_data保存下来(编译期完成这样一个映射表)std::array<void (variant::* const)(), sizeof...(Types)> destory_functions {&variant::destory_data<Types>...};// 到了运行期,根据当前实际的index_值,选择对应的析构方法if (data_ != nullptr) {(this->*destory_functions.at(index_))();std::free(data_);}
}// 实现拷贝构造函数
template <typename... Types>
variant<Types...>::variant(const variant &va): index_(va.index_) {// 静态期保存所有类型的构造函数std::array<void (variant<Types...>::*)(const void *), sizeof...(Types)> create_functions {&variant<Types...>::create_data<Types>...};// 动态时根据index_调用对应的构造(this->*create_functions.at(index_))(va.data_);
}// 实现拷贝赋值函数
template <typename... Types>
variant<Types...> &variant<Types...>::operator =(const variant &va) {// 先析构现有数据std::array<void (variant<Types...>::*)(), sizeof...(Types)> destory_functions {&variant<Types...>::destory_data<Types>...};(this->*destory_functions.at(index_))();// 再重新构造std::array<void (variant<Types...>::*)(const void *), sizeof...(Types)> create_functions {&variant<Types...>::create_data<Types>...};(this->*create_functions.at(index_))(va.data_);return *this;
}// 实现移动构造函数
template <typename... Types>
variant<Types...>::variant(variant &&va): index_(va.index_), data_(va.index_) {// 上面直接浅拷贝即可,然后把被移动的data_置空va.data_ = nullptr;va.index_ = -1; // 标记为不合法值
}// 实现移动赋值函数
template <typename... Types>
variant<Types...> &variant<Types...>::operator =(variant &&va) {// 先析构现有数据std::array<void (variant<Types...>::*)(), sizeof...(Types)> destory_functions {&variant<Types...>::destory_data<Types>...};(this->*destory_functions.at(index_))();// 再浅拷贝data_ = va.data_;index_ = va.index_;// 把被移动的data_置空va.data_ = nullptr;va.index_ = -1; // 标记为不合法值return *this;
}// 私有方法的实现
template <typename... Types>
template <typename Type>
void variant<Types...>::create_data(const void *obj) {// 用data_指针按照对应的类型调用拷贝构造new(data_) Type(*static_cast<const Type *>(obj));
}template <typename... Types>
template <typename Type>
void variant<Types...>::destory_data() {// 用data_指针按照对应的类型调用析构函数static_cast<std::add_pointer_t<Type>>(data_)->~Type();
}// 公有方法的实现
// 获取序号
template <typename... Types>
int variant<Types...>::index() const {return index_;
}// 取出数据(这个功能在std::variant中其实实现在std::get中)
template <typename... Types>
template <size_t Index>
auto variant<Types...>::get() const -> std::add_lvalue_reference_t<std::decay_t<typename get_type_by_index<Index, Types...>::type>> {using data_type = std::decay_t<typename get_type_by_index<Index, Types...>::type>;return *static_cast<data_type *>(data_);
}
小结
这一篇以一个「多选一」结构为例,带大家体验了一下模板元编程的实际使用方式,当然,这还没完,下一篇我们会针对这个多选一结构写一个「成员访问器」,会用到更多模板元编程的技巧。
相关文章:
C++模板元编程详细教程(之九)
前序文章请看: C模板元编程详细教程(之一) C模板元编程详细教程(之二) C模板元编程详细教程(之三) C模板元编程详细教程(之四) C模板元编程详细教程(之五&…...
PhysioNet2017分类的代码实现
PhysioNet2017数据集介绍可参考文章:https://wendy.blog.csdn.net/article/details/128686196。本文主要介绍利用PhysioNet2017数据集对其进行分类的代码实现。 目录一、数据集预处理二、训练2.1 导入数据集并进行数据裁剪2.2 划分训练集、验证集和测试集2.3 设置训…...
正大期货本周财经大事抢先看
美国1月CPI、Fed 等央行官员谈话 美国1月超强劲的非农就业人口,让投资人开始上修对这波升息循环利率顶点的预测,也使本周二 (14 日) 的美国 1月 CPI 格外受关注。 介绍正大国际期货主账户对比国内期货的优势 第一点:权限都在主账户 例如…...
html+css综合练习一
文章目录一、小米注册页面1、要求2、案例图3、实现效果3.1、index.html3.2、style.css二、下午茶页面1、要求2、案例图3、index.html4、style.css三、法国巴黎页面1、要求2、案例图3、index.html4、style.css一、小米注册页面 1、要求 阅读下列说明、效果图,进行静…...
安装jdk8
目录标题一、下载地址(一)Linux下载(二)Win下载二、安装(一)Linux(二)Win三、卸载(一)Linux(二)Win一、下载地址 jdk8最新版 jdk8其他…...
二分法心得
原教程见labuladong 首先,我们建议左右区间全部用闭区间。那么第一个搜索区间:left0; rightlen-1; 进入while循环,结束条件是right<left。 然后求mid,如果nums[mid]的值比target大,说明target在左边,…...
Linux安装Docker完整教程
背景最近接手了几个项目,发现项目的部署基本上都是基于Docker的,幸亏在几年前已经熟悉的Docker的基本使用,没有抓瞎。这两年随着云原生的发展,Docker在云原生中的作用使得它也蓬勃发展起来。今天这篇文章就带大家一起实现一下在Li…...
备份基础知识
备份策略可包括:– 整个数据库(整个)– 部分数据库(部分)• 备份类型可指示包含以下项:– 所选文件中的所有数据块(完全备份)– 只限自以前某次备份以来更改过的信息(增量…...
C++学习记录——팔 内存管理
文章目录1、动态内存管理2、内存管理方式operator new operator delete3、new和delete的实现原理1、动态内存管理 C兼容C语言关于内存分配的语法,而添加了C独有的东西。 //int* p1 (int*)malloc(sizeof(int));int* p1 new int;new是一个操作符,C不再需…...
Spring事务失效原因分析解决
文章目录1、方法内部调用2、修饰符3、非运行时异常4、try…catch捕获异常5、多线程调用6、同时使用Transactional和Async7、错误使用事务传播行为8、使用的数据库不支持事务9、是否开启事务支持在工作中,经常会碰到一些事务失效的坑,基于遇到的情况&…...
4个月的测试经验,来面试就开口要17K,面试完,我连5K都不想给他.....
2021年8月份我入职了深圳某家创业公司,刚入职还是很兴奋的,到公司一看我傻了,公司除了我一个测试,公司的开发人员就只有3个前端2个后端还有2个UI,在粗略了解公司的业务后才发现是一个从零开始的项目,目前啥…...
python学习之pyecharts库的使用总结
pyecharts官方文档:https://pyecharts.org//#/zh-cn/ 【1】Timeline 其是一个时间轴组件,如下图红框所示,当点击红色箭头指向的“播放”按钮时,会呈现动画形式展示每一年的数据变化。 data格式为DataFrame,数据如下图…...
【taichi】利用 taichi 编写深度学习算子 —— 以提取右上三角阵为例
本文以取 (bs, n, n) 张量的右上三角阵并展平为向量 (bs, n*(n1)//2)) 为例,展示如何用 taichi 编写深度学习算子。 如图,要把形状为 (bs,n,n)(bs,n,n)(bs,n,n) 的张量,转化为 (bs,n(n1)2)(bs,\frac{n(n1)}{2})(bs,2n(n1)) 的向量。我们先写…...
二进制 k8s 集群下线 worker 组件流程分析和实践
文章目录[toc]事出因果个人思路准备实践当前 worker 节点信息将节点标记为不可调度驱逐节点 pod将 worker 节点从 k8s 集群踢出下线 worker 节点相关组件事出因果 因为之前写了一篇 二进制 k8s 集群下线 master 组件流程分析和实践,所以索性再写一个 worker 节点的缩…...
Bean的六种作用域
限定程序中变量的可用范围叫做作用域,Bean对象的作用域是指Bean对象在Spring整个框架中的某种行为模式~~ Bean对象的六种作用域: singleton:单例作用域(默认) prototype:原型作用域(多例作用域…...
Http发展历史
1 缘起 有一次,听到有人在议论招聘面试的人员, 谈及应聘人员的知识深度,说:问了一些关于Http的问题,如Http相关结构、网络结构等, 然后又说,问没问相关原理、来源? 我也是有些困惑了…...
高级Java程序员必备的技术点,你会了吗?
很多程序员在入行之后的前一两年,快速学习到了做项目常用的各种技术之后,便进入了技术很难寸进的平台期。反正手里掌握的一些技术对于应付普通项目来说,足够用了。因此也会缺入停滞,最终随着年龄的增长,竞争力不断下降…...
【暴力量化】查找最优均线
搜索逻辑 代码主要以支撑概率和压力概率来判断均线的优劣 判断为压力: 当日线与测试均线发生金叉或即将发生金叉后继续下行 判断为支撑: 当日线与测试均线发生死叉或即将发生死叉后继续上行 判断结果的天数: 小于6日均线,用金叉或…...
Java读取mysql导入的文件时中文字段出现�??的乱码如何解决
今天在写程序时遇到了一个乱码问题,困扰了好久,事情是这样的, 在Mapper层编写了查询语句,然后服务处调用,结果控制器返回一堆乱码 然后查看数据源头处: 由重新更改解码的字符集,在数据库中是正…...
k8s核心概念—Pod Controller Service介绍——20230213
文章目录一、Pod1. pod概述2. pod存在意义3. Pod实现机制4. pod镜像拉取策略5. pod资源限制6. pod重启机制7. pod健康检查8. 创建pod流程9. pod调度二、Controller1. 什么是Controller2. Pod和Controller关系3. deployment应用场景4. 使用deployment部署应用(yaml&a…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)
cd /home 进入home盘 安装虚拟环境: 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境: virtualenv myenv 3、激活虚拟环境(激活环境可以在当前环境下安装包) source myenv/bin/activate 此时,终端…...
Matlab实现任意伪彩色图像可视化显示
Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中,如何展示好看的实验结果图像非常重要!!! 1、灰度原始图像 灰度图像每个像素点只有一个数值,代表该点的亮度(或…...
Appium下载安装配置保姆教程(图文详解)
目录 一、Appium软件介绍 1.特点 2.工作原理 3.应用场景 二、环境准备 安装 Node.js 安装 Appium 安装 JDK 安装 Android SDK 安装Python及依赖包 三、安装教程 1.Node.js安装 1.1.下载Node 1.2.安装程序 1.3.配置npm仓储和缓存 1.4. 配置环境 1.5.测试Node.j…...
Git 命令全流程总结
以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结,按操作场景分类整理: 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...
