C++基础与深度解析 | 元编程 | 元编程的编写方式 | 减少实例化技巧

文章目录

  • 一、元编程的引入
  • 二、顺序、分支、循环代码的编写方式
    • 1.顺序代码的编写方式
    • 2.分支代码的编写方式
    • 3.循环代码的编写方式
  • 三、减少实例化的技巧

这一章写的既浅又乱,为了知识的完整性先传上来,之后会重构

一、元编程的引入

  泛型编程提供了一种方式来编写通用的、类型无关的代码。然而,当需要针对某些特殊情况进行优化或引入额外的处理逻辑时,泛型编程可能就不够用了。这时,元编程可以作为一个补充,允许开发者在编译时根据类型或其他条件生成或选择代码。例如,你可以定义一个模板,它在编译时检查类型是否满足某个特性(如是否是POD类型),然后根据这个特性来选择不同的实现。

  • 元编程与编译期计算

    下面解释了如何使用编译期运算来辅助运行期计算:

    • 编译期计算的优势

      • 编译期计算可以在程序运行之前完成,这意味着不需要在程序执行时消耗计算资源。
      • 编译期计算可以利用类型信息和模板参数来生成最优化的代码。
    • 编译期与运行期的决策

      • 需要仔细分析哪些计算可以在编译期完成,哪些必须在运行期进行。
      • 编译期计算通常适用于那些不依赖于运行时数据的计算,如类型属性的计算、常量表达式的求值等。
    • 编译期与运行期的结合

      • 元编程不是简单地将计算一分为二,而是需要根据具体情况来决定哪些部分应该在编译期完成,哪些部分保留到运行期。
      • 例如,如果一个算法的性能关键部分可以在编译期确定,那么这部分可以作为模板的一部分来实现。而对于依赖于运行时输入的部分,则需要在运行期处理。
    • 运行期确定的信息

      • 如果某种信息需要在运行期确定,比如用户输入或外部数据,那么这部分通常无法利用编译期计算。
      • 但是,即使在这种情况下,也可以使用编译期计算来辅助运行期计算,比如通过模板元编程来生成运行期使用的代码或数据结构。
    • 模板元编程示例

      • 假设你有一个模板函数,它根据传入的类型参数来决定使用哪种算法。这种决策可以在编译期完成,而算法的具体实现则在运行期执行。
      • 另一个例子是使用模板特化来实现类型特征的检测,如检查一个类型是否具有特定的成员函数或属性,然后在编译期根据这些特征生成相应的代码。
    • 性能优化

      • 通过将尽可能多的计算移至编译期,可以减少运行时的开销,提高程序的执行效率。
      • 同时,编译期生成的代码可以针对特定情况进行优化,比如展开循环、消除冗余操作等。
    • 编译期错误检查

      编译期计算还可以用于错误检查和诊断,比如在编译时检测类型不匹配或参数错误,从而避免运行时错误。

元程序的形式

  C++中的元编程是一种在编译时执行代码生成和计算的技术。以下是C++元编程的一些主要形式:

  • 模板

    模板是C++中实现元编程的核心机制。通过模板,可以定义函数和类,它们可以处理任意类型或值。模板在编译时实例化,根据提供的类型或值参数生成具体的代码。

    #include <iostream>
    #include <type_traits>template <typename T>
    void print_type_name() {std::cout << "Type: " << typeid(T).name() << std::endl;
    }int main() {print_type_name<int>();    // 编译时确定类型名称print_type_name<double>();return 0;
    }
    
  • constexpr函数

    constexpr 函数是一种可以在编译时计算并返回常量表达式的函数。这些函数对于实现编译时计算非常有用,因为它们的结果可以被编译器优化和内联。

    #include <iostream>constexpr int add(int a, int b) {return a + b;
    }int main() {const int result = add(3, 5); // 编译时计算结果std::cout << "Result: " << result << std::endl;return 0;
    }
    
  • 编译期可使用的函数

    除了模板和 constexpr 函数,还有一些内建的编译期函数可以用于元编程,例如:

    • sizeof:返回一个类型或对象所占的字节数。

      int main() 
      { return sizeof(int);  //
      } 
      
    • alignof:返回类型所需的最小对齐字节数。

通常以函数为单位,也被称为函数式编程

元数据:(元程序的输入数据)

  • 基本元数据:数值、类型、模板
  • 数组

元程序的性质

  • 输入输出均为 “ 常量 ”
  • 函数无副作用

type_traits元编程库

详细内容可参考:https://en.cppreference.com/w/cpp/header/type_traits

这里面的函数都是编译期可使用的函数

  • C++11 引入到标准中,用于元编程的基本组件

二、顺序、分支、循环代码的编写方式

  下面介绍元编程的编写方式。

1.顺序代码的编写方式

  • 类型转换:去掉引用并添加const

    示例:

    #include <iostream>
    #include <type_traits>template <typename T, unsigned S>
    struct Fun
    {using remRef = typename std::remove_reference<T>::type;using type = typename std::add_const<remRef>::type;
    };int main()
    {Fun<int&, 3>::type x = 3;   //const int x = 3;
    }
    
  • 代码无需至于函数中

    通常置于模板中,以头文件的形式提供

  • 更复杂的示例:

    • 以数值、类型、模板作为输入
    • 以数值、类型、模板作为输出

    示例:

    #include <iostream>
    #include <type_traits>template <typename T, unsigned S>
    struct Fun
    {using remRef = typename std::remove_reference<T>::type;constexpr static bool value = (sizeof(T) == 5);};int main()
    {constexpr bool res = Fun<int&, 4>::value;std::cout << res << "\n";
    }
    
  • 引入限定符防止误用

    使用限定符(如constvolatile)可以帮助防止代码的误用

  • 通过别名模板简化调用方式

    #include <iostream>
    #include <type_traits>template <typename T, unsigned S>
    struct Fun
    {using remRef = typename std::remove_reference<T>::type;constexpr static bool value = (sizeof(T) == 5);};//使用别名模版
    template <typename T, int S>
    constexpr auto Fun_1 = Fun<T, S>::value;int main()
    {constexpr bool res = Fun_1<int&, 4>;std::cout << res << "\n";
    }
    

    std::is_same举例,详细可参考:https://zh.cppreference.com/w/cpp/types/is_same。

2.分支代码的编写方式

  下面介绍六种元编程分支代码的编写方式。

  • 基于 if constexpr 的分支:便于理解只能处理数值,同时要小心引入运行期计算

    if constexpr 是C++17引入的特性,它允许在编译时根据条件编译不同的代码分支。这使得模板代码更加灵活。

    #include <iostream>template <typename T>
    void process(T value) {if constexpr (std::is_integral<T>::value) {// 仅当T是整数类型时编译此分支std::cout << "Integral type" << std::endl;} else {// 其他类型std::cout << "Non-integral type" << std::endl;}
    } int main()
    {process<int>(100);  //Integral type
    }
    

    经编译后的代码为:

    #include <iostream>template<typename T>
    void process(T value)
    {if constexpr(std::is_integral<T>::value) {std::operator<<(std::cout, "Integral type").operator<<(std::endl);} else /* constexpr */ {std::operator<<(std::cout, "Non-integral type").operator<<(std::endl);} }/* First instantiated from: insights.cpp:16 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void process<int>(int value)
    {if constexpr(true) {std::operator<<(std::cout, "Integral type").operator<<(std::endl);} else /* constexpr */ {} }
    #endifint main()
    {process<int>(100);return 0;
    }
    
  • 基于(偏)特化引入分支:常见分支引入方式但书写麻烦

    模板特化可以用来为特定的类型或值提供定制化的实现,这是一种常见的分支引入方式。

    #include <iostream>template <typename T>
    struct Processor;// 对于其他类型,使用默认实现
    template <typename T>
    struct Processor {void process(T value) {// 默认实现std::cout << "process(T value)" << std::endl;}
    };template <>
    struct Processor<int> {void process(int value) {// 针对int的特化实现std::cout << "process(int value)" << std::endl;}
    };int main()
    {Processor<int> p;p.process(100);  //process(int value)Processor<double> p1;p1.process(3.14); //process(T value)
    }
    
  • 基于 std::conditional 引入分支:语法简单但应用场景受限

    详细内容可参考:https://en.cppreference.com/w/cpp/types/conditional

    B 在编译时为 true 则定义为 T,或若 B 为 false 则定义为 F

    std::conditional 是一个编译时条件选择器,可以用来基于模板参数选择不同的类型。

    #include <iostream>
    #include <type_traits>
    #include <typeinfo>int main() 
    {using Type1 = std::conditional<true, int, double>::type;using Type2 = std::conditional<false, int, double>::type;using Type3 = std::conditional<sizeof(int) >= sizeof(double), int, double>::type;std::cout << typeid(Type1).name() << '\n';std::cout << typeid(Type2).name() << '\n';std::cout << typeid(Type3).name() << '\n';
    }
    

    运行结果:

    i
    d
    d
    
  • 基于 SFINAE 引入分支

    • 基于 std::enable_if 引入分支:语法不易懂但功能强大

      详细内容可参考:https://zh.cppreference.com/w/cpp/types/enable_if

      std::enable_if 可以用来根据条件启用或禁用模板实例化。

      #include <iostream>template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>  //为true时等于void
      void process_integral(T value) {// 仅当T是整数类型时启用此函数std::cout << "111" << std::endl;
      }int main()
      {process_integral<int>(100);
      }
      

      注意用做缺省模板实参不能引入分支!

    • 基于 std::void_t 引入分支: C++17 中的新方法,通过 “无效语句” 触发分支

      std::void_t 是C++17引入的,可以用来在SFINAE中创建一个空类型,从而在条件不满足时使模板实例化失败。

  • 基于 concept 引入分支: C++20 中的方法

    可用于替换 enable_if

    template <typename T>
    concept Integral = std::is_integral<T>::value;template <Integral T>
    void process(T value) {// 仅当T满足Integral概念时编译此分支
    }
    
  • 基于三元运算符引入分支: std::conditional 的数值版本

    #include <iostream>
    #include <type_traits>template <int x>
    constexpr auto fun = (x < 100) ? x*2 : x-3;constexpr auto x = fun<102>();int main()
    {std::cout << x << std::endl;
    }
    

3.循环代码的编写方式

  C++元编程中的循环代码编写方式,这是一种在编译时执行循环的技术。

  • 简单的示例:计算二进制中包含 1 的个数

    #include <iostream>
    #include <type_traits>template <int x>
    constexpr auto fun = (x % 2) + fun<x / 2>;template<>
    constexpr auto fun<0> = 0;constexpr auto x = fun<99>;int main()
    {std::cout << x << std::endl;
    }
    

    经编译器处理可得到

    #include <iostream>
    #include <type_traits>template<int x>
    constexpr const auto fun = (x % 2) + fun<x / 2>;template<>
    constexpr const int fun<99> = (99 % 2) + fun<49>;
    template<>
    constexpr const int fun<49> = (49 % 2) + fun<24>;
    template<>
    constexpr const int fun<24> = (24 % 2) + fun<12>;
    template<>
    constexpr const int fun<12> = (12 % 2) + fun<6>;
    template<>
    constexpr const int fun<6> = (6 % 2) + fun<3>;
    template<>
    constexpr const int fun<3> = (3 % 2) + fun<1>;
    template<>
    constexpr const int fun<1> = (1 % 2) + fun<0>;template<>
    constexpr const int fun<0> = 0;constexpr const int x = fun<99>;int main()
    {std::cout.operator<<(x).operator<<(std::endl);return 0;
    }
    
  • 在编译期通常会使用递归来实现循环

  • 任何一种分支代码的编写方式都对应相应的循环代码编写方式

三、减少实例化的技巧

  • 为什么要减少实例化

    • 提升编译速度,减少编译所需内存
  • 相关技巧

    • 提取重复逻辑以减少实例个数
    • conditional使用时避免实例化
    • 使用std::conjunction / std::disjunction 引入短路逻辑
  • 其他技巧介绍

    • 减少分摊复杂度的数组元素访问操作

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

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

相关文章

【庞加莱几何-02】反演定理和证明

文章目录 一、说明二、 inversion和 reflection三、圆反演的定义四、广义的圆反演成圆 关键词&#xff1a;inversion、reflection 一、说明 这里是庞加莱几何的第二篇文章&#xff0c;是庞加莱基本几何属性的研究。本篇主要说清楚&#xff0c;什么是反演&#xff0c;在反演情况…

【面试官】知道synchronized锁升级吗

一座绵延在水上的美术馆——白鹭湾巧克力美术馆。它漂浮于绿水之上&#xff0c;宛如一条丝带轻盈地伸向远方 文章目录 可重入锁synchronized实现原理 synchronized缺点保存线程状态锁升级锁升级优缺点 1. 可重入锁 面试官&#xff1a;知道可重入锁有哪些吗? 可重入意味着获取…

HTTPS缺失?如何轻松解决IP地址访问时的“不安全”警告

一、问题现象 如果访问网站时出现以下任何一种情况&#xff0c;则说明该网站需要立即整改&#xff1a; 1.浏览器地址栏那里出现“不安全”字样&#xff1b; 2.小锁标志被红叉&#xff08;&#xff09;、斜线&#xff08;&#xff3c;&#xff09;等标志为不可用&#xff1b;…

Python牛市熊市横盘机制 | 缺口分析 | 头寸调整算法

&#x1f3af;要点 &#x1f3af;长短期方式&#xff1a;&#x1f58a;标准普尔指数一年期看涨/看跌走势 | &#x1f58a;软银美元基准纳斯达克走势。 &#x1f3af;市场机制&#xff1a;&#x1f58a;代码定义市场&#xff0c;彩色图表绘制多种机制 | &#x1f58a;牛市熊市…

sub_mch_id 与 sub_appid 不匹配怎么解决

小程序在支付的时候&#xff0c;有时候会碰到&#xff1a;sub_mch_id 与 sub_appid 不匹配的问题。这个问题意味着小程序微信支付时所使用的 sub_mch_id&#xff08;子商户号&#xff09;和 sub_appid&#xff08;小程序的appId&#xff09;不对应。下面就具体介绍如何核对是否…

AI-知识库搭建(二)GPT-Embedding模型使用

上一篇&#xff1a;AI-知识库搭建&#xff08;一&#xff09;腾讯云向量数据库使用-CSDN博客 一、Embedding模型 Embedding模型是一种将高维度的离散数据&#xff08;如文本、图像、音频等&#xff09;映射到低维度的连续向量空间的技术。这种技术广泛应用于自然语言处理&…

武汉理工大学嵌入式系统应用之临时抱佛脚复习

其实大学很多课程的期末冲刺复习非常简单&#xff0c;就是在大脑中构建一个redis数据库就行了&#xff0c;缓存下一大堆键值对&#xff0c;然后考试的时候输出&#xff0c;很没意思。 嵌入式系统的定义 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软件硬件可裁剪…

LabVIEW控制PLC的实现方式

LabVIEW与PLC的结合可以充分发挥两者的优点&#xff0c;实现更高效、灵活和可靠的自动化控制系统。本文将详细介绍LabVIEW控制PLC的实现方式&#xff0c;包括通信接口、数据交换、编程方法及实际应用案例&#xff0c;帮助用户理解并应用这一技术。 通信接口 常见通信协议 La…

WHAT - 富文本编辑器系列(一)

目录 一、介绍1.1 丰富的功能基本功能媒体和文件链接和锚点表格嵌入对象编辑和协作&#xff08;高级功能&#xff09;可访问性和国际化&#xff08;高级功能&#xff09;插件和扩展安全性集成和兼容性 1.2 流行的前端富文本编辑器1. TinyMCE2. CKEditor3. Quill【大厂推荐】4. …

LabVIEW与PLC的区别

LabVIEW和PLC是工业自动化领域中常见的两种控制和测控方案&#xff0c;各自有独特的优点和适用场景。本文将从多角度比较两者&#xff0c;帮助用户在选择控制系统时做出更明智的决策。 技术背景 LabVIEW LabVIEW是由National Instruments公司开发的图形化编程环境&#xff0…

【集装箱调度】基于粒子群算法实现考虑重量限制和时间约束的集装箱码头满载AGV自动化调度附matlab代码

% 交叉定位 - 最小二乘法定位算法模拟 % 参数设置 numIterations 1000; % 模拟迭代次数 maxDistance 1000; % 最远定位距离&#xff08;设定范围&#xff09; speedOfSound 343; % 声速&#xff08;单位&#xff1a;m/s&#xff09; % 预警机坐标 source [0, 0]; % 初始…

ChatGPT-4o, 腾讯元宝,通义千问对比测试中文文化

国内的大模型应用我选择了国内综合实力最强的两个&#xff0c;一个是腾讯元宝&#xff0c;一个是通义千问。其它的豆包&#xff0c;Kimi&#xff0c;文心一言等在某些领域也有强于竞品的表现。 问一个中文文化比较基础的问题,我满以为中文文化chatGPT不如国内的大模型。可事实…

2021 hnust 湖科大 操作系统课设 报告+原代码+指导书+流程图源文件

2021 hnust 湖科大 操作系统课设 报告原代码指导书流程图源文件 详情 目录 验证类实验&#xff1a; 1 实验一&#xff1a;Windows进程管理 1 一、 实验题目&#xff1a; 1 二、 实验目的 1 三、 实验内容 1 四、 实验结果与分析 2 五、 小结与心得体会 5 实验二&#xff1a;L…

【知识拓展】HTTP、WebSocket 和 RPC:区别与使用场景详解

在工作中&#xff0c;HTTP、WebSocket 和 RPC 是三种常见的协议或通信方式&#xff0c;根据资料查阅&#xff0c;本文主要记录它们的区别及其适用的使用场景 HTTP&#xff08;超文本传输协议&#xff09; 概述 HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一…

Springboot使用redis分布式锁的方法

要想使用redis分布式锁很好地支撑我们的业务 需要确保&#xff1a; 1.加锁要保证原子性&#xff1b; 2.解锁要保证原子性。 示例代码&#xff1a; public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock() {//使用redis分布式锁&#xff…

Javascript全解(基础篇)

语法与数据类型 语法 var\let\const var 声明一个变量&#xff0c;可选初始化一个值。 let 声明一个块作用域的局部变量&#xff0c;可选初始化一个值。 const 声明一个块作用域的只读常量。 用 var 或 let 语句声明的变量&#xff0c;如果没有赋初始值&#xff0c;则其值为 …

人工智能系统越来越擅长欺骗我们?

人工智能系统越来越擅长欺骗我们&#xff1f; 一波人工智能系统以他们没有被明确训练过的方式“欺骗”人类&#xff0c;通过为他们的行为提供不真实的解释&#xff0c;或者向人类用户隐瞒真相并误导他们以达到战略目的。 发表在《模式》(Patterns)杂志上的一篇综述论文总结了之…

店匠科技亮相VivaTech,新零售解决方案引关注

在中法建交60周年之际,两国关系持续发展并共同推动双方在人工智能和全球治理领域达成重要合作。同时,浙江-法国高新产业创新合作对接会在巴黎顺利举行,进一步促进了中法两国在高新技术领域的交流与合作。 紧跟此次访问的步伐,众多中国科技创新企业齐聚巴黎,于5月22日至25日在法…

浅谈安全用电管理系统对重要用户的安全管理

1用电安全管理的重要性   随着社会经济的不断发展&#xff0c;电网建设力度的不断加大&#xff0c;供电的可靠性和供电质量日益提高&#xff0c;电网结构也在不断完善。但在电网具备供电的条件下&#xff0c;部分高危和重要电力用户未按规定实现双回路电源线路供电&#xff1…

代码随想录算法训练营第五十三天 | 309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费

309.最佳买卖股票时机含冷冻期 视频讲解&#xff1a;动态规划来决定最佳时机&#xff0c;这次有冷冻期&#xff01;| LeetCode&#xff1a;309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bilibili代码随想录 解题思路 1. dp[i][0] 第i天持有股票的状态 dp[i][1]第i天不持股的状…