C++模板元编程:编译时的魔法
1. 引言
在C++的世界中,模板元编程是一种在编译时执行计算的强大技术。它允许开发者编写高度灵活和高效的代码,这些代码可以在不牺牲性能的前提下,根据类型和值的不同而变化。本文将深入探讨模板元编程的奥秘,并展示如何在现代C++开发中利用这一技术。
2. C++模板基础
C++模板是泛型编程的基石,它们提供了一种编写与数据类型无关的代码的方法。在这一节中,我们将深入探索模板的基本概念、使用方式以及它们在C++中的多种应用。
2.1 模板的定义和使用
模板允许我们定义可以处理多种数据类型的函数或类。下面是一个简单的函数模板示例,它演示了如何打印任意类型的数据:
#include <iostream>
#include <string>template <typename T>
void printValue(T value) {std::cout << value << std::endl;
}int main() {printValue(10); // 打印整数printValue(3.14); // 打印浮点数printValue("Hello"); // 打印字符串return 0;
}
2.2 函数模板和类模板的区别
函数模板定义了操作数据的函数,而类模板定义了可以包含数据和行为的类型。以下是类模板的一个示例:
template <typename T>
class Stack {
private:T* elements;size_t size;size_t capacity;public:Stack(size_t initialCapacity);~Stack();void push(const T& element);T pop();size_t getSize() const;
};
这个Stack类模板可以用于创建任何类型的栈。
2.3 模板参数和模板特化
模板参数允许我们定义通用的代码,而模板特化则允许我们为特定的类型提供特定的实现。以下是模板特化的一个例子:
// 通用的print函数模板
template <typename T>
void print(T value) {std::cout << value << std::endl;
}// 特化版本,用于打印字符串
template <>
void print(const std::string& value) {std::cout << "'" << value << "'" << std::endl;
}int main() {print(123); // 使用通用版本print("test"); // 使用特化版本return 0;
}
2.4 模板参数的默认值
我们可以为模板参数提供默认值,这样在调用模板时可以省略某些参数:
template <typename T, typename Allocator = std::allocator<T>>
class Vector {// ...
};
在这个例子中,如果用户没有指定分配器类型,Vector类模板将默认使用std::allocator。
2.5 非类型模板参数
除了类型参数外,模板还可以接受非类型参数,如整数:
template <int size>
class FixedSizeArray {T (&data)[size];
public:FixedSizeArray(T* array) {for (int i = 0; i < size; ++i) {data[i] = array[i];}}
};
这个FixedSizeArray类模板创建了一个固定大小的数组的引用。
2.6 模板的友元声明
模板可以声明友元函数或类,这些友元可以访问模板的私有成员:
template <typename T>
class MyClass {
private:T privateData;public:template <typename U>friend class MyOtherClass;
};template <typename T>
class MyOtherClass {
public:void accessPrivateData(MyClass<T>& obj) {// 可以访问obj.privateData}
};
2.7 模板与继承
模板可以与继承一起使用,创建灵活的类型层次结构:
template <typename T>
class Base {virtual void doSomething() = 0;
};template <typename T>
class Derived : public Base<T> {void doSomething() override {// 实现细节}
};
在这个例子中,Derived类模板继承自Base类模板,并提供了doSomething的实现。
3. 模板元编程的基本原理
模板元编程是C++中一种高级技术,它允许开发者在编译时进行类型检查和计算。这种技术可以极大地提高程序的性能和灵活性。在本节中,我们将深入探讨模板元编程的基本原理,并提供丰富的示例来展示其应用。
3.1 编译时计算
模板元编程的核心是编译时计算。这意味着所有与类型相关的逻辑和决策都在编译阶段完成,从而避免了运行时的开销。以下是一个简单的示例,展示如何在编译时计算两个类型的平均大小:
template <typename T1, typename T2>
struct AverageSize {static const std::size_t value = (sizeof(T1) + sizeof(T2)) / 2;
};int main() {std::cout << AverageSize<int, double>::value << std::endl;// 输出 int 和 double 平均大小的字节数return 0;
}
3.2 模板元编程与运行时计算的区别
模板元编程与运行时计算的主要区别在于执行时机和性能。运行时计算在程序执行时进行,而模板元编程在编译时完成。这使得模板元编程可以提供更高的性能,因为它避免了运行时的类型检查和决策。
3.3 元组和变参模板的使用
元组和变参模板是模板元编程中两个重要的概念。元组允许我们以类型安全的方式存储和操作一系列不同类型的数据,而变参模板则允许我们编写接受任意数量参数的模板。
3.3.1 元组的使用
C++17引入了std::tuple,它是一个可以存储不同类型的固定大小序列。以下是一个使用元组的示例:
#include <tuple>template <typename... Types>
std::tuple<Types...> make_tuple(Types... args) {return std::make_tuple(args...);
}int main() {auto myTuple = make_tuple(1, 'a', 3.14);// myTuple 是一个包含 int, char 和 double 的 tuplereturn 0;
}
3.3.2 变参模板的使用
变参模板允许我们定义接受任意数量参数的模板。以下是一个变长参数求和的示例:
template <typename T>
T sum(T t) {return t;
}template <typename T, typename... Types>
T sum(T first, Types... rest) {return first + sum(rest...);
}int main() {std::cout << sum(1, 2, 3, 4) << std::endl;// 输出 10return 0;
}
3.4 递归模板模式
递归模板模式是模板元编程中的一个强大工具,它允许我们通过递归的方式进行类型和值的操作。以下是一个使用递归模板模式实现的阶乘计算:
template <int N>
struct Factorial {static const int value = N * Factorial<N - 1>::value;
};template <>
struct Factorial<0> {static const int value = 1;
};int main() {std::cout << Factorial<5>::value << std::endl;// 输出 120return 0;
}
3.5 模板元编程中的类型特性
类型特性(Type Traits)是模板元编程中用于类型属性查询的一种技术。例如,我们可以检查一个类型是否是指针类型:
template <typename T>
struct is_pointer : std::false_type {};template <typename T>
struct is_pointer<T*> : std::true_type {};int main() {std::cout << is_pointer<int>::value << std::endl; // 输出 0std::cout << is_pointer<int*>::value << std::endl; // 输出 1return 0;
}
3.6 模板元编程的限制
尽管模板元编程非常强大,但它也有一些限制,比如编译时间的增加和模板深度限制。以下是一个示例,展示如何处理模板深度限制:
template <typename T, T v>
struct integral_constant {static const T value = v;typedef T value_type;typedef integral_constant<T, v> type;
};typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;// 使用递归展开减少模板深度
template <bool...> struct bool_pack;template <bool... values>
struct all_true : true_type {};template <bool first, bool... rest>
struct all_true<first, rest...>: integral_constant<bool, first && all_true<rest...>::value> {};int main() {std::cout << all_true<true, true, true>::value << std::endl; // 输出 1std::cout << all_true<true, false, true>::value << std::endl; // 输出 0return 0;
}
4. 深入模板元编程
模板元编程不仅仅是在编译时进行计算,它还涉及到一系列高级技术,这些技术可以让我们编写出更加强大和灵活的代码。本节将深入探讨一些高级的模板元编程技巧,并提供丰富的示例来展示它们的应用。
4.1 高级模板技巧
4.1.1 SFINAE(Substitution Failure Is Not An Error)
SFINAE是一个强大的模板编程技术,它允许我们根据类型的特性来启用或禁用某些模板实例化。以下是一个使用SFINAE的示例:
#include <type_traits>template <typename T>
using add_const_t = typename std::add_const<T>::type;template <typename T>
auto func(T& t) -> add_const_t<decltype(t++)> {return t++;
}int main() {int i = 0;static_assert(std::is_same<decltype(func(i)), int&>::value, "Should be int&");return 0;
}
4.1.2 模板特化和偏特化
模板特化和偏特化允许我们为特定的类型或类型组合提供定制化的实现:
template <typename T1, typename T2>
struct Pair { /* ... */ };template <typename T>
struct Pair<T, T> {// 为相同类型的Pair提供特化实现
};template <typename T1>
struct Pair<T1, int> {// 为第二个类型为int的Pair提供偏特化实现
};
4.2 模板递归和模板展开
4.2.1 模板递归
模板递归是一种在模板定义中使用自身的方式,它可以用于实现递归算法:
template <int N>
struct Factorial {static constexpr int value = N * Factorial<N - 1>::value;
};template <>
struct Factorial<0> {static constexpr int value = 1;
};
4.2.2 模板展开
模板展开允许我们在编译时展开模板实例,这可以用于优化性能:
template <int... Ints>
struct IntSequence {};template <int N, int... Ints>
struct MakeIntSequence {using type = typename MakeIntSequence<N - 1, N - 1, Ints...>::type;
};template <int... Ints>
struct MakeIntSequence<0, Ints...> {using type = IntSequence<Ints...>;
};template <int N>
using MakeIntSequence_t = typename MakeIntSequence<N>::type;
4.3 模板元编程在算法实现中的应用
4.3.1 编译时排序
使用模板元编程,我们可以在编译时对数据进行排序,从而避免运行时的开销:
template <int... Ints>
struct SortedInts;template <int First, int Second, int... Rest>
struct SortedInts<First, Second, Rest...> {using type = typename std::conditional<First < Second,typename SortedInts<First, Rest...>::type,typename SortedInts<Second, First, Rest...>::type>::type;
};template <int Int>
struct SortedInts<Int> {using type = IntSequence<Int>;
};
4.3.2 编译时查找
模板元编程也可以用于在编译时执行查找操作:
template <int N, int... Ints>
struct FindIndex;template <int N, int Head, int... Tail>
struct FindIndex<N, N, Head, Tail...> {static const int value = 1;
};template <int N, int Head, int... Tail>
struct FindIndex<N, Head, Tail...> {static const int value = 1 + FindIndex<N, Tail...>::value;
};template <int N>
struct FindIndex<N> {static const int value = -1;
};
4.4 模板元编程的高级应用
4.4.1 元组算法
使用模板元编程,我们可以对元组中的元素执行算法,如应用函数或变换:
template <typename F, typename Tuple, std::size_t... Is>
void apply_impl(F&& f, Tuple&& t, std::index_sequence<Is...>) {using swallow = int[];(void)swallow{0, (void(f(std::get<Is>(std::forward<Tuple>(t)))), 0)...};
}template <typename F, typename Tuple>
void apply(F&& f, Tuple&& t) {apply_impl(std::forward<F>(f), std::forward<Tuple>(t),std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value>{});
}
4.4.2 表达式模板
表达式模板是一种使用模板元编程来延迟计算的技术,它常用于库设计中以提高性能:
template <typename T>
class Vector {// ...
public:template <typename U>Vector(const Vector<U>& other) {// ...}Vector operator+(const Vector& other) const {return Vector(*this).add(other);}private:Vector& add(const Vector& other) {// 实现向量加法return *this;}
};
5. 模板元编程的实用技巧
在深入探索模板元编程之后,我们需要了解一些实用的技巧来提高我们的编程实践。这些技巧将帮助我们编写更清晰、更高效且易于维护的模板代码。
5.1 编写可读性强的模板代码
5.1.1 清晰的模板参数命名
使用描述性的模板参数名称,以提高代码的可读性。
template <typename ElementType, typename AllocatorType>
class MyVector {// ...
};
5.1.2 使用类型别名
类型别名可以简化复杂的类型表达。
template <typename T>
using Ptr = std::shared_ptr<T>;Ptr<SomeClass> myPtr = std::make_shared<SomeClass>();
5.2 调试模板元编程代码的策略
5.2.1 编译器警告和错误
学会阅读和理解编译器的警告和错误信息。
5.2.2 简化模板
将复杂的模板分解为更小的部分,逐一进行测试。
5.2.3 使用static_assert
static_assert可以在编译时检查条件,帮助调试。
template <typename T>
class MyClass {static_assert(std::is_integral<T>::value, "T must be an integral type");// ...
};
5.3 性能优化和陷阱
5.3.1 避免模板膨胀
过度使用模板可能导致模板膨胀,增加编译时间和二进制大小。
5.3.2 内联模板函数
使用inline关键字或模板内联变量来减少函数调用开销。
template <typename T>
inline T addOne(T value) {return value + 1;
}
5.3.3 模板实例化
明智地选择模板实例化,避免不必要的实例化。
5.4 利用constexpr进行编译时计算
5.4.1 constexpr函数
使用constexpr函数进行编译时计算。
template <typename T>
constexpr T pi = T(3.14159265358979323846);template <typename T>
constexpr T calculateCircleArea(T radius) {return pi<T> * radius * radius;
}
5.5 模板元编程与C++标准库
5.5.1 使用标准库中的Type Traits
利用<type_traits>中的类型特性来编写更灵活的模板代码。
template <typename T>
using MaybeConst = typename std::conditional<std::is_const<T>::value, const T, T
>::type;
5.5.2 利用标准库中的元组
使用<tuple>和<utility>中的元组来处理多种类型的数据。
#include <tuple>template <typename... Args>
std::tuple<Args...> make_tuple_impl(Args&&... args) {return std::make_tuple(std::forward<Args>(args)...);
}template <typename... Args>
auto make_tuple(Args&&... args) {return make_tuple_impl(std::forward<Args>(args)...);
}
5.6 高级模板技术
5.6.1 模板模板参数
使用模板模板参数来参数化模板的模板。
template <template <typename> class TT>
class Wrapper {
public:template <typename T>TT<T> wrapped;
};
5.6.2 变长模板参数的递归展开
使用递归展开技术来处理变长模板参数。
template <typename... Args>
void processArgs(Args... args) {(..., processArg(std::forward<Args>(args)));
}template <typename Arg>
void processArg(Arg&& arg) {// Process single argument
}
5.7 模板元编程的最佳实践
5.7.1 保持模板的简洁性
避免在模板中使用复杂的逻辑,以减少编译时间和潜在的错误。
5.7.2 利用编译时断言
使用static_assert来确保模板的使用符合预期。
5.7.3 编写模板友好的代码
确保你的代码可以很好地与其他模板代码协同工作。
相关文章:
C++模板元编程:编译时的魔法
1. 引言 在C的世界中,模板元编程是一种在编译时执行计算的强大技术。它允许开发者编写高度灵活和高效的代码,这些代码可以在不牺牲性能的前提下,根据类型和值的不同而变化。本文将深入探讨模板元编程的奥秘,并展示如何在现代C开发…...
SQL进阶day10————多表查询
目录 1嵌套子查询 1.1月均完成试卷数不小于3的用户爱作答的类别 1.2月均完成试卷数不小于3的用户爱作答的类别 编辑1.3 作答试卷得分大于过80的人的用户等级分布 2合并查询 2.1每个题目和每份试卷被作答的人数和次数 2.2分别满足两个活动的人 3连接查询 3.1满足条件…...
debug调试_以Pycharm为例
文章目录 作用步骤打断点调试调试窗口 作用 主要是检查逻辑错误,而非语法错误。 步骤 打断点 在需要调试的代码行前打断点,执行后会停顿在断点位置(不运行) 调试 右键“debug”,或者直接点击右上角的小虫子 调试…...
wms第三方海外仓系统:如何为中小型海外仓注入新活力
对于中小型海外仓来说,想在大型集团海外仓同台竞争中获得优胜,提升其管理效率是非常关键的一环。 我们所熟知的wms系统,也就是第三方成熟海外仓系统,正是这些海外仓企业提升管理水平、降低成本的重要工具。 1、wms第三方海外仓系…...
html是什么?http是什么?
html Html是什么?http是什么? Html 超文本标记语言;负责网页的架构; http((HyperText Transfer Protocol)超文本传输协议; https(全称:Hypertext Transfer Protocol …...
L1-007 念数字js实现
异步解法 const readline require("readline"); const rl readline.createInterface({input: process.stdin,output: process.stdout, }); const input_arr [];//储存数据 rl.on(line, function (line) {input_arr.push(line); } ); rl.on(close, function () {/…...
Perl 运算符
Perl 运算符 Perl 是一种功能强大的编程语言,广泛应用于系统管理、网络编程、GUI 创建、数据库访问等众多领域。Perl 的语法灵活,支持多种编程范式,包括过程式、面向对象和函数式编程。在 Perl 中,运算符扮演着重要的角色&#x…...
语法04 C++ 标准输入语句
标准输入 使用格式:cin >> 输入的意思就是把一个值放到变量里面去,也就是变量的赋值,这个值是由我们自己输入的。 (注意:输入变量前要先定义,输入完之后要按Enter键。) 输入多个变量,与输出类似,…...
python数据分析--- ch6-7 python容器类型的数据及字符串
python数据分析---ch6-7 python容器类型的数据及字符串 1. Ch6--容器类型的数据1.1 序列1.1.1 序列的索引操作1.1.2 加和乘操作1.1.3 切片操作1.1.4 成员测试 1.2 列表1.2.1 创建列表1.2.2 追加元素1.2.3 插入元素1.2.4 替换元素1.2.5 删除元素1.2.6 列表排序(1&…...
【Linux取经路】守护进程
文章目录 一、前台进程和后台进程二、Linux 的进程间关系三、setsid——将当前进程设置为守护进程四、daemon——设置为守护进程五、结语 一、前台进程和后台进程 Linux 中每一次用户登录都是一个 session,一个 session 中只能有一个前台进程在运行,键盘…...
Nginx之文件下载服务器
1.概述 在对外分享文件时,利用Nginx搭建一个简单的下 载文件管理服务器,文件分享就会变得非常方便。利 用Nginx的诸多内置指令可实现自动生成下载文件列表 页、限制下载带宽等功能。配置样例如下: server {listen 8080;server_name localhos…...
OpenCV学习(4.11) OpenCV中的图像转换
1. 目标 在本节中,我们将学习 使用OpenCV查找图像的傅立叶变换利用Numpy中可用的FFT功能傅立叶变换的一些应用我们将看到以下函数:**cv.dft()** ,**cv.idft()** 等 理论 傅立叶变换用于分析各种滤波器的频率特性。对于图像,使用…...
2024.6.13每日一题
LeetCode 子序列最大优雅度 题目链接:2813. 子序列最大优雅度 - 力扣(LeetCode) 题目描述 给你一个长度为 n 的二维整数数组 items 和一个整数 k 。 items[i] [profiti, categoryi],其中 profiti 和 categoryi 分别表示第 i…...
Linux命令详解(2)
文本处理是Linux命令行的重要应用之一。通过一系列强大的命令,用户可以轻松地对文本文件进行编辑、查询和转换。 cat: 这个命令用于查看文件内容。它可以一次性显示整个文件,或者分页显示。此外,cat 还可以用于合并多个文件的内容…...
iOS ReactiveCocoa MVVM
学习了在MVVM中如何使用RactiveCocoa,简单的写上一个demo。重点在于如何在MVVM各层之间使用RAC的信号来更方便的在各个层之间进行响应式数据交互。 demo需求:一个登录界面(登录界面只有账号和密码都有输入,登录按钮才可以点击操作)࿰…...
图文解析ASN.1中BER编码:结构类型、编码方法、编码实例
本文将详细介绍ASN.1中的BER编码规则,包括其编码机制、数据类型表示、以及如何将复杂的数据结构转换为二进制数据。通过本文的阅读,读者将对ASN.1中的BER编码有一个全面的理解。 目录 一.引言 二.BER编码基本结构 ▐ 1. 类型域(Type&#…...
jQuery如何停止动画队列
在jQuery中,你可以使用.stop()方法来停止动画队列。.stop()方法有几个可选的参数,可以用来控制停止动画的方式。 以下是.stop()方法的基本用法和一些参数选项: 无参数:立即停止当前动画,并跳到最后的状态。后续的动画…...
vue3+electron搭建桌面软件
vue3electron开发桌面软件 最近有个小项目, 客户希望像打开 网易云音乐 那么简单的运行起来系统. 前端用 Vue 会比较快一些, 因此决定使用 electron 结合 Vue3 的方式来完成该项目. 然而, 在实施过程中发现没有完整的博客能够记录从创建到打包的流程, 摸索一番之后, 随即梳理…...
oracle常用经典SQL查询
oracle常用经典SQL查询(转贴) oracle常用经典SQL查询 常用SQL查询: 1、查看表空间的名称及大小 select t.tablespace_name, round(sum(bytes/(1024*1024)),0) ts_size from dba_tablespaces t, dba_data_files d where t.tablespace_name d.tablespace_name grou…...
Android shell 常用 debug 命令
目录 1、查看版本2、am 命令3、pm 命令4、dumpsys 命令5、sed命令6、log定位查看APK进程号7、log定位使用场景 1、查看版本 1.1、Android串口终端执行 getprop ro.build.version.release #获取Android版本 uname -a #查看linux内核版本信息 uname -r #单独查看内核版本 1.2、…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
OpenGL-什么是软OpenGL/软渲染/软光栅?
软OpenGL(Software OpenGL)或者软渲染指完全通过CPU模拟实现的OpenGL渲染方式(包括几何处理、光栅化、着色等),不依赖GPU硬件加速。这种模式通常性能较低,但兼容性极强,常用于不支持硬件加速…...
react-pdf(pdfjs-dist)如何兼容老浏览器(chrome 49)
之前都是使用react-pdf来渲染pdf文件,这次有个需求是要兼容xp环境,xp上chrome最高支持到49,虽然说iframe或者embed都可以实现预览pdf,但为了后续的定制化需求,还是需要使用js库来渲染。 chrome 49测试环境 能用的测试…...
vue3 手动封装城市三级联动
要做的功能 示意图是这样的,因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…...
