C++20 高级编程

news2024/12/26 22:04:31

文章目录

  • 前言
  • 前奏
    • lambda
    • 浅谈std::ref的实现
    • 浅谈is_same
    • 浅谈std::function的实现
    • std::visit 与 std::variant 与运行时多态
    • SFINAE
    • 类型内省
    • 标签分发 (tag dispatching)
    • 软件设计六大原则 SOLID
  • To be continue....

前言

  • C++20 是C++在C++11 之后最大的一次语言变革, 其中引入了大量具有革命性的新特性.
  • 本节包含了C++20中相当重要的四大特性: 概念约束, ranges(范围)标准库, 协程以及模块
    • 概念约束:
             是一个编译期谓词, 它根据程序员定义的接口规范对类型,常量等进行编译时检查,以便在泛型编程中为使用者提供更好的可读性与错误信息.
    • ranges标准库:
             对现有的标准库进行了补充,它以函数式编程范式进行编程, 将计算任务分解成一系列灵活的原子操作, 使得代码的正确性更容易推理.
    • 协程:
             是一种可挂起, 可恢复的通用函数, 它的切换开销是纳秒级的, 相对其他方案而言占用的资源极低, 并且可以非侵入式地为已有库扩展协程接口, 它常常用于并发编程, 生成器, 流处理, 异常处理等.
    • 模块:
             解决了传统的头文件编译模型的痛点: 依赖顺序导致头文件难以组合, 重复解析, 符号覆盖等问题, 从语言层面为程序员提供了模块化的手段.
  • 涉及了一些元编程的概念

前奏

lambda

  • lambda其实是由编译器生成的一个匿名类
  • 如果lambda包含在捕获列表内, 那么捕获将在对应的匿名类中生成成员变量与构造函数来存储捕获;
  • 对于无捕获的lambda而言, 其生成的匿名类中拥有一个非虚的函数指针类型转换操作符, 能够将lambda转换成函数指针. 这个不难理解, 因为无状态的 lambda 表达式可以赋给无状态的函数指针;
  • 在C++20 中泛型 lambda 也支持以模板参数形式提供, 这样就能保证两个形参类型一致.
    auto add=[]<typename T>(T a,T b){return a+b;};
    cout<<add(1,2)<<endl;
  • 模板函数只有实例化之后才能传递, 而泛型 lambda 是一个对象,可以按值传递, 在调用时根据实际传参进行实例化 模板函数 operator(),从而延迟了实例化的时机, 大大提高了灵活性. 标准库的一些算法通常要求对函数对象进行组合, 此时泛型 lambda 将能通过编译, 而模板函数不行.

浅谈std::ref的实现

  • 就是说构造一个新的对象(reference_wrapper是一个类模板)并在内部保存原来传入的变量的地址 _f 和类型 type
  • 重载类型转换为原来变量的引用类型, 有了_f 和 type, 这样可以在需要的时候自动将reference_wrapper转换为原来传入的变量实体的引用;
  • reference_wrapper 她本身可能会被 decay 但内部存储的 _f 值始终不会变, 始终可以在需要的时候自动转换为 type&
  • 我们可以自己简单实现一下:
    template<typename T>
    class my_ref{
    public:
        T * _f;
      explicit  my_ref(T& var): _f(addressof(var)){
    
        };
        operator T& () { return *_f; };
    //如果传入的是一个可调用对象
        template<class... Args>
        auto&& operator()(Args&& ...args){
    	 return (*_f)(args...);
        }
    };
    
  • 参考: https://zhuanlan.zhihu.com/p/581739392

浅谈is_same

  • 这是C++11引入的, 其实很简单, 模板特化就行了. 让编译器决定. 考虑: 万一我要运行时才能确定类型呢?
    template<class T, class U> struct is_same : std::false_type {};
    //偏特化版本 待确认一个模板参数
    template<class T> struct is_same<T, T> : std::true_type {};
    

浅谈std::function的实现

  • 有点类似上面的std::ref
  • 主要是对函数参数列表类型的获取, 可以使用模板特化来达到目的 C++ Template -> [5] -> 自由函数与模板可变参数

std::visit 与 std::variant 与运行时多态

  • 用法参考:
    https://zhuanlan.zhihu.com/p/676918348
    https://zhuanlan.zhihu.com/p/670189611
  • std::variant 行为像是一个类型安全的联合体。它存储了一系列类型,并能够在运行时安全地处理这些类型之一。
  • 它保留足够的空间来存储其可能的任何类型,通常是这些类型中最大者的大小。此外,std::variant 还需要额外的存储空间来跟踪当前存储的类型。
  • 为了维护类型安全,std::variant 使用一个内部索引来标记当前激活的类型。当访问或修改 std::variant 的值时,它会检查这个索引,并确保操作符合当前激活的类型。
  • std::visit 的第一个参数传入一个可调用对象,后面传入的是可调用对象的参数(可以用variant)。 std::visit 的工作原理依赖于编程语言或编译器的内部机制,这些机制通常对程序员透明。其中一种可能的实现方式是使用 “vtable”(Virtual Table,虚函数表)。 总之, std::visit 会在运行时查找函数地址。每当创建一个 std::variant 对象的时候,就会产生一个与之关联的 vtable,同来存储这个 std::variant 中存储的相关信息。
  • visit 编译时静态绑定 (第二个参数中各类型对应的第一个参数中可调用函数版本绑定到类型对应的索引 (用vtable来存储映射关系) )
  • visit 运行时动态绑定 (检查variant中当前激活的类型的索引). 从而决定要调用的函数版本
  • subtype多态例子:
    #include <iostream>
    #include <memory>
    #include <cmath>
    namespace Subtype
    {
        struct Shape
        {
            virtual ~Shape() = default;
            virtual double getArea() const = 0;
            virtual double getPerimeter() const = 0;
        };
        
        struct Circle: Shape
        {
            Circle(double r): r_(r) {}
            double getArea() const override
            {
                return M_PI * r_ * r_;
            }
            double getPerimeter() const override
            {
                return 2 * M_PI * r_;
            }
        private:
            double r_;
        };
        
        struct Rectangle: Shape
        {
            Rectangle(double w, double h): w_(w), h_(h) {}
            double getArea() const override
            {
                return w_ * h_;
            }
            double getPerimeter() const override
            {
                return 2 * (w_ + h_);
            }
        private:
            double w_;
            double h_;
        };
    }
    using namespace Subtype;
    
    int main(int argc, char** argv) {
        std::unique_ptr<Shape> shape = std::make_unique<Circle>(2);
        // shape area: 12.5664 perimeter: 12.5664
        std::cout << "shape area: " << shape->getArea()
            << " perimeter: " << shape->getPerimeter() << std::endl;
    
        shape = std::make_unique<Rectangle>(2, 3);
        // shape area: 6 perimeter: 10
        std::cout << "shape area: " << shape->getArea()
            << " perimeter: " << shape->getPerimeter() << std::endl;
    
        return 0;
    }
    
  • ad-hoc多态例子:
    #include <variant>
    #include <cmath>
    #include <iostream>
    namespace Adhoc
    {
        struct Circle
        {
            double r;
        };
        // Circle的一系列操作
        double getArea(const Circle& c)
        {
            return M_PI * c.r * c.r;
        }
        double getPerimeter(const Circle& c)
        {
            return 2 * M_PI * c.r;
        };
    
        struct Rectangle
        {
            double w;
            double h;
        };
        // Rectangle的一系列操作
        double getArea(const Rectangle& r)
        {
            return r.w * r.h;
        }
        double getPerimeter(const Rectangle& r)
        {
            return 2 * (r.w + r.h);
        };
    
        // 通过加法类型定义一个统一的类型Shape,其拥有不同的形状,从而实现运行时多态
        using Shape = std::variant<Circle, Rectangle>;
    
        // 统一类型Shape的一系列多态行为
        double getArea(const Shape& s)
        {
            return std::visit([](const auto & data)
            {
                return getArea(data);
            }, s);
        }
        double getPerimeter(const Shape& s)
        {
            return std::visit([](const auto & data)
            {
                return getPerimeter(data);
            }, s);
        };
    }
    using namespace Adhoc;
    
    int main(int argc, char** argv) {
        Shape shape = Circle{2};
        // shape area: 12.5664 perimeter: 12.5664
        std::cout << "shape area: " << getArea(shape)
            << " perimeter: " << getPerimeter(shape) << std::endl;
    
        shape = Rectangle{2, 3};
        // shape area: 6 perimeter: 10
        std::cout << "shape area: " << getArea(shape)
            << " perimeter: " << getPerimeter(shape) << std::endl;
        return 0;
    }
    
  • image

    subtype 多态和 ad-hoc 多态的表现形式对比

    多态形式定义多态调用
    subtype 多态Abstract* objobj->method()
    ad-hoc 多态Abstract objmethod(obj)

SFINAE

  • substitution failure is not an error

  • 典型的用法是利用enable_if
    enable_if:

    template<bool, typename _Tp = void>
      struct enable_if
      { };
    
    // Partial specialization for true.
    template<typename _Tp>
      struct enable_if<true, _Tp>//第二个参数默认为void
      { typedef _Tp type; };
    

    如果是false, 那么第一个空类没有type成员, 这时直接使用其type将报错
    但在SFINAE决策上下文环境中, 这种情况可以做为一种决策条件:

    下面情况下, 编译器如果根据实参推断发现enable_if<false>没有定义成员类型type, 将导致替换失败, 将其从候选集中删除, 从而达到我们的目的

    template<class T, enable_if_t<is_integral_v<T>>* = nullptr>
    void test(T t)
    {cout << "is integral" << endl;};
    
    template<class T, enable_if_t<is_floating_point_v<T>>* = nullptr>
    void test(T t)
    {cout << "is floating_point" << endl;};
    
  • Tips: 函数重载的过程中只看函数的声明, 如果它被决策为最佳可行函数, 但模板函数体内发生了模板参数替换失败, 那么就会在实例化过程中产生编译错误, 而不是SFINAE

类型内省

  • 检查对象的类型或属性的一种能力
  • 在C++中类型萃取也可以视作内省
  • 就能实现把各种类型各种分离与组合, <type_trait> 实现了这些功能
  • 比如 C++ Template -> [5] -> 自由函数与模板可变参数 中也是利用了类型内省 类似的还有数组 template<class E,size_t N> someArr<E[N]>{…}

标签分发 (tag dispatching)

  • 除了 enable_if 之外的编译时多态手段还有标签分发, 这也是C++社区著名的管用手法;
  • 标签常常是一个空类, 没有别的什么, 只是当作一种类型. 好让编译器在SFINAE决策中把它作为参考依据, 匹配出最合适的版本
  • 辅助类 true_type 和 false_type 类型也可视作标签, 他们把 true 和 false 包装成两种类型 这样在编译时就能决策出最合适的版本
  • 示例:
    template<class T>bool numEqImpl(T l, T r, true_type)
    {
        cout << "is floating" << endl;
        return true;
    }
    template<class T>bool numEqImpl(T l, T r, false_type)
    {
        cout << "is not floating" << endl;
        return false;
    }
    template<class T> enable_if_t<is_arithmetic_v<T>, bool> numEq(T l, T r)
    {
        return numEqImpl(l, r, is_floating_point<T> {});
    }
    
    怎么样, 是不是逐渐理解一切了?

软件设计六大原则 SOLID

  • Single responsibility principle
  • Open Closed Principle
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle
  • Dependence Inversion Principle
  • Law of Demeter
  • https://baike.baidu.com/starmap/view?nodeId=294b5d3c8cc7452ad5c9cdba&lemmaTitle=开闭原则&lemmaId=2828775&starMapFrom=lemma_starMap&fromModule=lemma_starMap

To be continue…

https://netcan.github.io/

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

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

相关文章

Python网络爬虫实战——实验7:Python使用apscheduler定时采集任务实战

【实验内容】 本实验主要介绍在Django框架中使用APScheduler第三方库实现对数据的定时采集。 【实验目的】 1、掌握APScheduler库的使用&#xff1b; 2、学习在Django中实现多个定时任务调度&#xff1b; 【实验步骤】 步骤1 Apscheduler简介与特点 步骤2 Apscheduler基本…

【开源】基于JAVA语言的公司货物订单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 客户管理模块2.2 商品维护模块2.3 供应商管理模块2.4 订单管理模块 三、系统展示四、核心代码4.1 查询供应商信息4.2 新增商品信息4.3 查询客户信息4.4 新增订单信息4.5 添加跟进子订单 五、免责说明 一、摘要 1.1 项目…

字符串相关的函数和内存块相关函数

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

概念抽取:构建认知基础的关键步骤

目录 前言1 概念抽取任务定义1.1 概念知识图谱的关系定义1.2 实体与概念的紧密关联1.3 多样的概念关系 2 概念在认知中的重要角色2.1 语言理解的基础2.2 上下位关系的深化理解 3 概念抽取方法3.1 基于模板的抽取3.2 基于百科的抽取3.3 基于机器学习的方法 4 应用4.1 自然语言理…

ENVI下基于知识决策树提取地表覆盖信息

基于知识的决策树分类是基于遥感影像数据及其他空间数据,通过专家经验总结、简单的数学统计和归纳方法等,获得分类规则并进行遥感分类。分类规则易于理解,分类过程也符合人的认知过程,最大的特点是利用的多源数据。 决策树分类主要的工作是获取规则,本文介绍使用CART算法…

C++力扣题目62--不同路径 63--不同路径II 343--整数拆分 96--不同的二叉搜索树

62.不同路径 力扣题目链接(opens new window) 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。…

leetcode hot100岛屿数量

本题中要求统计岛屿数量&#xff08;数字1的上下左右均为1&#xff0c;则是连续的1&#xff0c;称为一块岛屿&#xff09;。那么这种类型题都是需要依靠深度优先搜索&#xff08;DFS&#xff09;或者广度优先搜索&#xff08;BFS&#xff09;来做的。这两种搜索&#xff0c;实际…

DS:带头双向循环链表的实现(超详细!!)

创作不易&#xff0c;友友们给个三连吧&#xff01;&#xff01;&#xff01; 博主的上篇文章介绍了链表&#xff0c;以及单链表的实现。 单链表的实现&#xff08;超详细&#xff01;&#xff01;&#xff09; 其实单链表的全称叫做不带头单向不循环链表&#xff0c;本文…

uni-app 接口封装,token过期,自动获取最新的token

一、文件路径截图 2、新建一个文件app.js let hosthttp://172.16.192.40:8083/jeecg-boot/ //本地接口 let myApi {login: ${host}wx/wxUser/login, //登录 } module.exports myApi 3、新建一个文件request.js import myApi from /utils/app.js; export const r…

Linux ---- Shell编程之函数与数组

目录 一、函数 1、函数的基本格式 2、查看函数列表 3、删除函数 4、函数的传参数 5、函数返回值 实验&#xff1a; 1.判断输入的ip地址正确与否 2. 判断是否为管理员用户登录 6、函数变量的作用范围 7、函数递归&#xff08;重要、难点&#xff09; 实验&#xff1…

P1024 [NOIP2001 提高组] 一元三次方程求解————C++

目录 [NOIP2001 提高组] 一元三次方程求解题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 解题思路Code运行结果 [NOIP2001 提高组] 一元三次方程求解 题目描述 有形如&#xff1a; a x 3 b x 2 c x d 0 a x^3 b x^2 c x d 0 ax3bx2cxd0 这样的一个一元…

【2024-01-27可用】NVM安装太慢,镜像地址失效

安装nvm时&#xff0c; Could not retrieve https://registry.npm.taobao.org/latest/SHASUMS256.txt. 解决如下 ### 具体配置 安装路径 root: D:\Program Files\nvm path: D:\Program Files\nodejs镜像地址 node_mirror: https://npmmirror.com/mirrors/node/ npm_mirror:…

STL容器大总结区分(上)

如图所示 ,按大小说明其重要性 那就先说两个最重要的: vector---数组 list-----链表 vector 基本概念 功能&#xff1a; vector 数据结构和 数组非常 相似 &#xff0c;也称为 单端数组 vector 与普通数组区别&#xff1a; 不同之处在于数组是静态空间&…

vue3添加pinia

概述&#xff1a;Pinia 是一个专为 Vue.js 开发的状态管理库。Vue.js 是一个流行的 JavaScript 框架&#xff0c;用于构建用户界面。Pinia 旨在提供一个简单、灵活且性能高效的状态管理方案&#xff0c;使开发者能够更容易地管理应用的状态。 以下是 Pinia 的一些特点和概念&a…

在 React 组件中使用 JSON 数据文件,怎么去读取请求数据呢?

要在 React 组件中使用 JSON 数据&#xff0c;有多种方法。 常用的有以下几种方法&#xff1a; 1、直接将 JSON 数据作为一个变量或常量引入组件中。 import jsonData from ./data.json;function MyComponent() {return (<div><h1>{jsonData.title}</h1>&…

Vue3中ElementPlus组件二次封装,实现原组件属性、插槽、事件监听、方法的透传

本文以el-input组件为例&#xff0c;其它组件类似用法。 一、解决数据绑定问题 封装组件的第一步&#xff0c;要解决的就是数据绑定的问题&#xff0c;由于prop数据流是单向传递的&#xff0c;数据只能从父流向子&#xff0c;子想改父只能通过提交emit事件通知父修改。 父&a…

第十八讲_HarmonyOS应用开发实战(实现电商首页)

HarmonyOS应用开发实战&#xff08;实现电商首页&#xff09; 1. 项目涉及知识点罗列2. 项目目录结构介绍3. 最终的效果图4. 部分源码展示 1. 项目涉及知识点罗列 掌握HUAWEI DevEco Studio开发工具掌握创建HarmonyOS应用工程掌握ArkUI自定义组件掌握Entry、Component、Builde…

Leetcode—2942. 查找包含给定字符的单词【简单】

2023每日刷题&#xff08;一零一&#xff09; Leetcode—2942. 查找包含给定字符的单词 实现代码 class Solution { public:vector<int> findWordsContaining(vector<string>& words, char x) {vector<int> ans;for(int i 0; i < words.size(); i)…

JDK8新特性:Stream

Stream 认识Stream 也叫Stream流&#xff0c;是jdk8开始新增的一套API&#xff08;java.util.stream.*&#xff09;&#xff0c;可以用于操作集合或者数组的数据。优势&#xff1a;Stream流大量的结合了Lambda的语法风格来编程&#xff0c;提供了一种更强大&#xff0c;更加简…

TCS34725使用记录

TCS34725使用记录 1、IIC通信 1、tcs34725硬件通信采用标准的IIC协议&#xff1b; 2、在寄存器读写上需要注意一下&#xff0c;在读写寄存时&#xff0c;需要将地址最高位置1&#xff1b; I2C_SendByte(reg|0x80);//一般的iic操作寄存器都是直接传入reg 2、配置与数据读取 …