[Effective C++]条款48 模板元编程(TMP)

本文初发于 “天目中云的小站”,同步转载于此。

条款48 : 认识template元编程

在条款47我们主要了解了萃取器这种模板元编程, 也初步进入了模板元编程的世界. 在本条款中, 我们将继续认识模板元编程, 认识其必要性和应用场景, 相比于条款47讲的还算比较深入,本条款真的就只是简介, 因为其体量确实非常庞大, 甚至可以单独作为一个学科研究.

Template metaprogramming, 模板元编程, 简称TMP, 是编写template-based C++程序并执行于编译期的过程.


模板元编程的效用

我们目前可以用在条款47中学到的萃取器的知识来理解模板元的效用 :

  • 它让某些事情更容易, 这些事情原本比较困难甚至不可能.(例如针对迭代器类型进行可靠的条件编译)
  • 它将工作期从运行期移至编译期, 大大提高了运行效率, 有更小的可执行文件, 更短的运行期, 更少的内存消耗.

假设我们使用萃取器时不采用重载或if constexpr这种模板元编程, 而是就是在运行期通过判断迭代器类型来条件判断, 我们看看最后的效果怎样 :

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{if (typeid(typename std::iterator_traits<IterT>::iterator_category) ==typeid(std::random_access_iterator_tag)) {iter += d;                                     }                                                 else {if (d >= 0) { while (d--) ++iter; }             else { while (d++) --iter; }                    }                                                 
}

这里还是用萃取器提取出类型, 利用typeid在运行期进行条件判断, 但是这种方法不仅不高效(对应效用2), 而且不可行(对应效用1).

  • 不高效 : 这个很容易理解, 利用模板元编程在编译期即可实现的效果, 这样却要在运行期花时间判断.

  • 不可行 : 这段代码在一些情况下甚至都无法通过编译, 如果我们像下面这样使用 :

    std::list<int>::iterator iter;
    advance(iter, 10);
    

    这样使用非常合理, 但是会运行崩溃, 因为list的迭代器是双向迭代器, 而非随机访问迭代器, 所以iter += d;这段代码根本无法通过编译! 也许你会认为因为条件判断, 如果是list的迭代器, 这句代码永远不会触发, 但是我们应当知道 :

    • 编译器必须确保所有源码都有效, 纵使是不会执行起来的代码.

至此我们应该已经可以理解到部分模板元编程可以达到的效用了.


模板元编程中的"hello world!"

我们可以了解模板元编程中的一个入门编程, 它相当于初入编程的"hello world", 即在编译期计算阶乘.

template<unsigned n>                 
struct Factorial {                   enum { value = n * Factorial<n-1>::value };
};template<>                          
struct Factorial<0> {            // 全特化enum { value = 1 };
};

我们通过代码应该可以推导出一个递归的过程, 并且这个过程是通过模板在编译期来实现的! 于是我们就可以这样使用 :

int main()
{std::cout << Factorial<5>::value << std::endl;   // 直接打印出120std::cout << Factorial<10>::value;               // 直接打印出3628800
}

我们可以和普通递归求阶乘进行对比 :

  • 普通递归 : 运行期实现, 可以通过用户输入动态计算任意数的阶乘.
  • 模板元递归 : 编译期实现, 只可以得到预先设置的数的阶乘.

简单来说就是前者耗费运行期时间但是灵活, 后者不费运行期时间但是不可变.

当然使用enum是一个比较原始且可读性较差的做法, 在C++11已经引入constexpr :

template<unsigned n>
struct Factorial {static constexpr unsigned value = n * Factorial<n - 1>::value;  // constexpr代替enum
};template<>
struct Factorial<0> {static constexpr unsigned value = 1;
};

三个应用场景案例

  • 确保度量单位正确.

    在科学工程中, 我们可以提前确定度量单位的结合正确, 可以进行早期的错误侦测.

    #include <iostream>
    #include <type_traits>// 定义不同的单位类型
    struct Time { static constexpr const char* name = "Time"; };		// 时间
    struct Length { static constexpr const char* name = "Length"; };	// 长度// 计算单位的乘法
    template <typename Unit1, typename Unit2>
    struct MultiplyUnits;// 两个长度相乘得到面积
    template <>
    struct MultiplyUnits<Length, Length> {   // 全特化using type = struct Area { static constexpr const char* name = "Area"; }; // 定义面积类型
    };// 时间与长度相除,得到速度
    template <>
    struct MultiplyUnits<Length, Time> {using type = struct Velocity { static constexpr const char* name = "Velocity"; }; // 定义速度类型
    };// 打印单位名称
    template <typename Unit>
    void printType() {std::cout << "TypeName: " << Unit::name << std::endl;
    }int main() {// 计算长度与时间的组合,得到速度typedef typename MultiplyUnits<Length, Time>::type unit1;printType<unit1>();  // 输出: Unit: Velocity// 计算长度与长度的组合,得到面积typedef typename MultiplyUnits<Length, Length>::type unit2;printType<unit2>();  // 输出: Unit: Areareturn 0;
    }
    

    本例中就可以根据向MultiplyUnits中传入的类型在编译期进行判断其结果的类型.

  • 优化矩阵运算.

    在条款44中我们编写过矩阵, 假如我们进行下面的运算 :

    typedef SquareMatrix<double, 10000> BigMatrix;
    BigMatrix m1, m2, m3, m4, m5;              
    BigMatrix result = m1 * m2 * m3 * m4 * m5; 
    

    如果这些在运行期完成, 将会产生内存巨大的临时对象和不低的时间成本, 但假如用模板元编程就可能消除临时对象并合并循环, 大大降低成本, 具体细节书中没有给出, 这里就不再细讲.

  • 可以生成客户定制之设计模式实现品.

    这里的命题就更加广阔了, 简单理解就是许多设计模式都和类与模板有关, 可以利用模板元编程根据需求将一些设计模式的行为从运行期搬到编译期中, 不仅实现了定制, 还提高了运行效率.


现代模板元编程

随C++11, C++14, C++17的引入, 模板元编程的语法日渐丰富, 这一领域虽然有些晦涩难懂, 但是其确实有其价值所在, 并且越来越被重视. 我们虽然不一定要完全掌握, 但是可以逐步了解一下模板元编程的语法, 例如constexpr, if constexpr, SFINAE技术, 模板元函数等等.


请记住 :

  • 模板元编程可将工作由运行期移往编译期, 因而得以实现早期错误侦测和更高的执行效率.

by 天目中云

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/5636.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正则表达式的艺术:轻松驾驭 Python 的 re 库

目录 一、正则表达式的基本概念 二、Python 的 re 库简介 三、正则表达式的元字符 四、正则表达式的贪婪与非贪婪模式 五、实战案例 六、总结 正则表达式&#xff08;Regular Expression&#xff09;是文本处理中不可或缺的工具&#xff0c;它强大而灵活&#xff0c;能够…

炸场硅谷,大模型“蒸汽机”迎来“瓦特时刻”

作者 | 曾响铃 文 | 响铃说 中国大模型又在包括硅谷在内的全球AI圈炸场了。 两天前&#xff0c;幻方量化旗下AI公司深度求索&#xff08;DeepSeek&#xff09;&#xff0c;以及月之暗面相隔20分钟相继发布了自家最新版推理模型&#xff0c;分别是DeepSeek-R1以及Kimi 全新多…

备赛蓝桥杯之第十五届职业院校组省赛第二题:分享点滴

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

MyBatis最佳实践:提升数据库交互效率的秘密武器

第一章&#xff1a;框架的概述&#xff1a; MyBatis 框架的概述&#xff1a; MyBatis 是一个优秀的基于 Java 的持久框架&#xff0c;内部对 JDBC 做了封装&#xff0c;使开发者只需要关注 SQL 语句&#xff0c;而不关注 JDBC 的代码&#xff0c;使开发变得更加的简单MyBatis 通…

1. 基于图像的三维重建

1. 基于图像的三维重建 核心概念三维重建中深度图、点云的区别&#xff1f;深度图点云总结 深度图到点云还需要什么步骤&#xff1f;1. **获取相机内参**2. **生成相应的像素坐标**3. **计算三维坐标**4. **构建点云**5. **处理颜色信息&#xff08;可选&#xff09;**6. **去除…

将UI界面交给第三方库

当你了解了 Vue 项目构建和开发的基本知识后&#xff0c;我认为接下来你一定想亲自在构建出的项目中填充自己的业务和功能逻辑&#xff0c;因为目前其还是空白的。 但是这里我不会教你如何实现一个具体的业务和功能模块&#xff0c;因为每个人想要实现的东西都可能不尽相同。如…

深圳大学-计算机系统(3)-实验三取指和指令译码设计

实验目标 设计完成一个连续取指令并进行指令译码的电路&#xff0c;从而掌握设计简单数据通路的基本方法。 实验内容 本实验分成三周&#xff08;三次&#xff09;完成&#xff1a;1&#xff09;首先完成一个译码器&#xff08;30分&#xff09;&#xff1b;2&#xff09;接…

[Dialog屏幕开发] 屏幕绘制(文本/输入框/按钮控件)

阅读该篇文章之前&#xff0c;可先阅读下述资料 [Dialog屏幕开发] 设置GUI Status 菜单/GUI Title 标题https://blog.csdn.net/Hudas/article/details/145288453?spm1001.2014.3001.5501 上篇文章我们的屏幕已实现了如下功能 我们已经设置了GUI Status菜单以及GUI Title标题…

如何通过海外社交媒体平台提升品牌曝光度?

跨境电商和全球营销策略的核心之一是通过海外社交媒体平台提升品牌曝光度。为了实现这一目标&#xff0c;企业需要选定适合的社交平台并制定精准的营销策略。结合OKBrow指纹指纹浏览器的强大功能&#xff0c;您能够高效管理多个社交账户&#xff0c;避免平台识别账户之间的关联…

可替代CentOS 7的Linux操作系统选型

可替代CentOS 7的其他Linux操作系统选型 一、背景介绍二、主流操作系统调研2.1 企业级产品:Red Hat Enterprise Linux/CentOS Stream2.1.1 Red Hat Enterprise Linux2.1.2 CentOS Stream2.2 其他发行版:Debian/Ubuntu2.3 开源产品:AlmaLinux / RockyLinux2.3.1 AlmaLinux2.3…

每日一题洛谷P1423 小玉在游泳c++

#include<iostream> using namespace std; int main() {double s;cin >> s;int n 0;double sum 0;double k 2;while (sum < s) {sum k;n;k * 0.98;}cout << n << endl;return 0; }

DRG_DIP 2.0时代医院程序结构转型与数据结构优化研究

一、引言 1.1 DRG_DIP 2.0 改革背景与意义 医保支付方式改革在医疗保障制度改革中占据着极为关键的地位&#xff0c;是推动医疗领域变革的核心力量。它犹如一把精准的手术刀&#xff0c;对医疗资源的合理分配、医疗服务质量的稳步提升以及医疗费用的有效控制起着决定性作用。…

Redis支持数据类型详解

4 数据类型 Redis支持多种数据类型&#xff1a;string&#xff08;字符串&#xff09;&#xff0c;hash&#xff08;哈希&#xff09;&#xff0c;list&#xff08;列表&#xff09;&#xff0c;set&#xff08;集合&#xff09;、zset&#xff08;sorted set 有序集合&#x…

游戏设备升级怎么选?RTX4070独显,ToDesk云电脑更具性价比

过新年、添喜气&#xff01;正逢节期来临不知道各位是否都跟小编一样在考虑购置生活中的各样所需呐&#xff1f; 25年可谓是3A游戏大作之年&#xff0c;例如《GTA6》《文明7》《死亡搁浅2》《刺客信条&#xff1a;影》下半年落地的《塞尔达传说&#xff1a;新篇章》《生化危机9…

网络安全解决方案分享:推荐十款网络准入控制系统,保护企业网络安全

随着企业信息化进程的不断推进&#xff0c;企业网络安全面临的威胁愈加复杂。网络准入控制&#xff08;NAC, Network Access Control&#xff09;系统作为保障企业网络安全的核心技术&#xff0c;无论是防止外部攻击、阻止内部滥用&#xff0c;还是确保设备符合合规要求&#x…

WebSocket实现私聊私信功能

目录 后端pom.xmlConfig配置类Controller类DTO 前端安装相关依赖websocketService.js接口javascripthtmlCSS 效果展示简单测试连接&#xff1a; 报错解决方法1、vue3 使用SockJS报错 ReferenceError: global is not defined 后面将继续完善&#xff0c;待更新... 后端 pom.xml…

【PHP】部署和发布PHP网站到IIS服务器

欢迎来到《小5讲堂》 这是《PHP》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言安装PHP 稳定版本线程安全版解压使用 PHP配置 配置文件扩展文件…

电梯系统的UML文档07

从这个类中得到的类图&#xff0c;构划出了软件的大部分设计。 系统结构视图提供软件和整个系统结构最复杂的也是最优雅的描述。和通常的软件系统相比&#xff0c;在分布式嵌入系统中了解系统组件如何协同工作是非常重要的。毕竟&#xff0c;每个类图仅仅是一个系统的静态设计…

低代码系统-产品架构案例介绍、明道云(七)

今天分析另外一个零代码、低代码产品-明道云&#xff0c;跟所有低代码产品的架构图一样&#xff0c;高、大、炫、美。 依然是从下至上&#xff0c;从左到右的顺序。 开发层 搭建中心 表单、流程、报表、用户中心&#xff0c;还是这些内容&#xff0c;自定义打印很多平台都有&am…

Linux编译安装Netgen/NGSolve

本文记录Linux下编译安装Netgen/NGSolve的流程。 零、环境 操作系统Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1oneAPI2024.2.1 一、安装依赖 1.1 VS Code 下载并安装VS Code&#xff0c;然后安装以下插件&#xff0c; Task Explorer Output Colorizer …