C++模板(第二版)笔记之第十八章:模板的多态性

news2025/1/12 15:43:54

文章目录

  • 一、动态多态(dynamic polymorphism)
  • 二、静态多态
  • 三、静态多态VS动态多态
    • 1.术语
    • 2.优点和缺点
    • 3.结合两种多态形式:CRTP
  • 四、使用concepts
  • 五、新形势的设计模式
  • 六、泛型编程
  • 七、总结

一、动态多态(dynamic polymorphism)

动态多态:继承和虚函数实现;

静态多态:模板也允许我们用单个统一符号将不同的特定行为关联起来, 不过该关联主要发生在编译期间;

#include "coord.hpp"
// common abstract base class GeoObj for geometric objects
class GeoObj
{
public:
    // draw geometric object:
    virtual void draw() const = 0;
    // return center of gravity of geometric object:
    virtual Coord center_of_gravity() const = 0;
    virtual ~GeoObj() = default;
};

// concrete geometric object class Circle
// - derived from GeoObj
class Circle : public GeoObj
{
public:
    virtual void draw() const override;
    virtual Coord center_of_gravity() const override;
};

// concrete geometric object class Line
// - derived from GeoObj
class Line : public GeoObj
{
public:
    virtual void draw() const override;
    virtual Coord center_of_gravity() const override;
};

// draw any GeoObj
void myDraw(GeoObj const &obj)
{
    obj.draw(); // call draw() according to type of object
}
// compute distance of center of gravity between two GeoObjs
Coord distance(GeoObj const &x1, GeoObj const &x2)
{
    Coord c = x1.center_of_gravity() - x2.center_of_gravity();
    return c.abs(); // return coordinates as absolute values
}

// draw heterogeneous collection of GeoObjs,处理异质集合中不同类型的对象
void drawElems(std::vector<GeoObj *> const &elems)
{
    for (std::size_type i = 0; i < elems.size(); ++i)
    {
        elems[i]->draw(); // call draw() according to type of element
    }
}
/*在通过基类的指针或者引用调用一个虚函数的时候, 所调用的函数将是指针或者引用所指对象的真正类型中的相应函数*/
int main()
{
    Line l;
    Circle c, c1, c2;
    myDraw(l);                  // myDraw(GeoObj&) => Line::draw()
    myDraw(c);                  // myDraw(GeoObj&) => Circle::draw()
    distance(c1, c2);           // distance(GeoObj&,GeoObj&)
    distance(l, c);             // distance(GeoObj&,GeoObj&)
    std::vector<GeoObj *> coll; // heterogeneous collection
    coll.push_back(&l);         // insert line
    coll.push_back(&c);         // insert circle
    drawElems(coll);            // draw different kinds of GeoObjs
}

二、静态多态

模板也可以被用来实现多态。 不同的是, 它们不依赖于对基类中公共行为的分解。 取而代之的是, 这一“共性( commonality) ” 隐式地要求不同的“形状( shapes) ” 必须支持使用了相同语法的操作。

  • eg:相关函数的名字必须相同
  • 比较 myDraw()的两种实现, 可以发现其主要的区别是将 GeoObj 用作模板参数而不是公共基类。
  • 但是, 在表象之下还有很多区别:使用动态多态的话, 在运行期间只有一个 myDraw()函数, 但是在使用模板的情况下, 却会有多种不同的函数, 例如 myDraw<Line>()和myDraw<Circle>()
比如, 上一节中的 myDraw():
void myDraw (GeoObj const& obj) // GeoObj is abstract base
class
{
	obj.draw();
}
//也可以被实现成下面这样:


template<typename GeoObj>
void myDraw (GeoObj const& obj) // GeoObj is template parameter
{
	obj.draw();
}
  • 动态多态的eg改造成静态多态
#include "coord.hpp"
#include <vector>

// concrete geometric object class Circle
// - not derived from any class
class Circle
{
public:
    void draw() const;
    Coord center_of_gravity() const;
};

// concrete geometric object class Line
// - not derived from any class
class Line
{
public:
    void draw() const;
    Coord center_of_gravity() const;
}

// draw any GeoObj
template <typename GeoObj>
void myDraw(GeoObj const &obj)
{
    obj.draw(); // call draw() according to type of object
}

// compute distance of center of gravity between two GeoObjs
template <typename GeoObj1, typename GeoObj2>
Coord distance(GeoObj1 const &x1, GeoObj2 const &x2)
{
    Coord c = x1.center_of_gravity() - x2.center_of_gravity();
    return c.abs(); // return coordinates as absolute values
}

// draw homogeneous collection of GeoObjs
template <typename GeoObj>
void drawElems(std::vector<GeoObj> const &elems)
{
    for (unsigned i = 0; i < elems.size(); ++i)
    {
        elems[i].draw(); // call draw() according to type of element
    }
}

int main()
{
    Line l;
    Circle c, c1, c2;
    myDraw(l);        // myDraw<Line>(GeoObj&) => Line::draw()
    myDraw(c);        // myDraw<Circle>(GeoObj&) =>Circle::draw()
    distance(c1, c2); // distance<Circle,Circle>(GeoObj1 &, GeoObj2 &)
    /*
    引入了两个模板参数, GeoObj1 和 GeoObj2, 来支持不同类型的集合对象之间的距离计算:
    */
    distance(l, c); // distance<Line,Circle>(GeoObj1&,GeoObj2&)
    /*我们将不再能够透明地处理异质容器。 这也正是 static 多态中的 static部分带来的限制: 所有的类型必须在编译期可知。
    不过, 我们可以很容易的为不同的集合对象类型引入不同的集合。 这样就不再要求集合的元素必须是指针类型,
    这对程序性能和类型安全都会有帮助*/
    // std::vector<GeoObj*> coll; //ERROR: no heterogeneous collection possible
    std::vector<Line> coll; // OK: homogeneous collection possible
    coll.push_back(l);      // insert line
    drawElems(coll);        // draw all lines
}

三、静态多态VS动态多态

1.术语

Static 和 dynamic 多态提供了对不同 C++编程术语的支持:

  • 通过继承实现的多态是有界的(bounded) 和动态的(dynamic) :

有界的意思是, 在设计公共基类的时候, 参与到多态行为中的类型的相关接口就已经确定(该概念的其它一些术语是侵入的(invasive 和 intrusive) ) 。

动态的意思是, 接口的绑定是在运行期间执行的。

  • 通过模板实现的多态是无界的(unbounded) 和静态的(static) :

无界的意思是, 参与到多态行为中的类型的相关接口是不可预先确定的(该概念的其它一些术语是非侵入的(noninvasive 和 nonintrusive) )

静态的意思是, 接口的绑定是在编译期间执行的

动态多态:有界动态多态;
静态多态:无界静态多态;

2.优点和缺点

C++中的动态多态有如下优点:

可以很优雅的处理异质集合。
可执行文件的大小可能会比较小(因为它只需要一个多态函数, 不像静态多态那样, 需要为不同的类型进行各自的实例化) 。
代码可以被完整的编译; 因此没有必须要被公开的代码(在发布模板库时通常需要发布模板的源代码实现)

C++中 static 多态的优点:

内置类型的集合可以被很容易的实现。 更通俗地说, 接口的公共性不需要通过公共基类实现。
产生的代码可能会更快(因为不需要通过指针进行重定向, 先验的(priori) 非虚函数通常也更容易被 inline) 。
即使某个具体类型只提供了部分的接口, 也可以用于静态多态, 只要不会用到那些没有被实现的接口即可。

通常认为静态多态要比动态多态更类型安全(type safe) , 因为其所有的绑定都在编译期间进行了检查。

  • 例如, 几乎不用担心将一个通过模板实例化得到的、 类型不正确的对象插入到一个已有容器中(编译期间会报错) 。 但是, 对于一个存储了指向公共基类的指针的容器,其所存储的指针却有可能指向一个不同类型的对象。

3.结合两种多态形式:CRTP

为了能够操作集合对象的异质集合, 你可以从一个公共基类中派生出不同的集合对象。 而且, 你依然可以使用模板为某种形式的集合对象书写代码。

一个成员函数的虚拟性是如何被参数化的, 以及我们是如何通过 curiously recurring template pattern(CRTP) 为 static多态提供额外的灵活性的。

四、使用concepts

模板的静态多态的一个巨大问题:

  • 接口的绑定是通过实例化相应的模板执行的。 也就是说没有可供编程的公共接口或者公共 class。 取而代之的是, 如果所有实例化的代码都是有效的, 那么对模板的任何使用也都是有效的。 否则, 就会导致难以理解的错误信息, 或者是产生了有效的代码却导致了意料之外的行为。

  • 基于这一原因, C++语言的设计者们一直在致力于实现一种能够为模板参数显式地提供(或者是检查) 接口的能力。 在 C++中这一接口被称为 concept。 它代表了为了能够成功的实例化模板, 模板参数必须要满足的一组约束条件。

  • Concept 可以被理解成静态多态的一类“接口”

template <typename T>
concept GeoObj = requires(T x) {
    { x.draw() } -> void;
    { x.center_of_gravity()} -> Coord;
};

#include <vector>
//clang-format off code
/*
用关键字 concept 定义了一个 GeoObj concept, 它要求一个类型要有可被调用
的成员函数 draw()和 center_of_gravity(), 同时也对它们的返回类型做了限制。
*/
template <typename T>
concept GeoObj = requires(T x) {
                     {
                         x.draw()
                         } -> void;
                     {
                         x.center_of_gravity()
                         } -> Coord;
                 };

//clang-format on code

// 使用 requires 子句要求模板参数满足GeoObj concept
// draw any GeoObj
template <typename T>
    requires GeoObj<T>
void myDraw(T const &obj)
{
    obj.draw(); // call draw() according to type of object
}

// compute distance of center of gravity between two GeoObjs
template <typename T1, typename T2>
    requires GeoObj<T1> && GeoObj<T2>
Coord distance(T1 const &x1, T2 const &x2)
{
    Coord c = x1.center_of_gravity() - x2.center_of_gravity();
    return c.abs(); // return coordinates as absolute values
}

// draw homogeneous collection of GeoObjs
template <typename T>
    requires GeoObj<T>
void drawElems(std::vector<T> const &elems)
{
    for (std::size_type i = 0; i < elems.size(); ++i)
    {
        elems[i].draw(); // call draw() according to type of element
    }
}

// 对于那些可以参与到静态多态行为中的类型, 该方法依然是非侵入的:
// concrete geometric object class Circle
// - not derived from any class or implementing any interface
class Circle
{
public:
    void draw() const;
    Coord center_of_gravity() const;
};

五、新形势的设计模式

桥接模式:在不同的接口实现之间做切换。

动态多态的桥接模式:

  • 根据[DesignPatternsGoF], 桥接模式通常是通过使用一个接口类实现的, 在这个接口类中包
    含了一个指向具体实现的指针, 然后通过该指针委派所有的函数调用(参见图 18.3) 。

静态多态的桥接模式:

  • 但是, 如果具体实现的类型在编译期间可知, 我们也可以利用模板实现桥接模式
  • 这样做会更类型安全(一部分原因是避免了指针转换) , 而且性能也会更好
    在这里插入图片描述

六、泛型编程

STL 是一个框架, 它提供了许多有用的操作(称为算法) , 用于对象集合(称为容器) 的一些线性数据结构。 算法和容器都是模板。

但是, 关键的是算法本身并不是容器的成员函数。 算法被以一种泛型的方式实现,因此它们可以用于任意的容器类型(以及线性的元素集合)。

为了实现这一目的, STL的设计者们找到了一种可以用于任意线性集合、 称之为迭代器(iterators) 抽象概念。

  • 从本质上来说, 容器操作中针对于集合的某些方面已经被分解到迭代器的功能中。
  • eg:这样我们就可以在不知道元素的具体存储方式的情况下, 实现一种求取序列中元素最大值
    的方法
template <typename Iterator>
Iterator max_element(Iterator beg, // refers to start of collection
                     Iterator end) // refers to end of collection
{
    // use only certain Iterator operations to traverse all elements
    // of the collection to find the element with the maximum value
    // and return its position as Iterator}
/*
容器本身只要提供一个能够遍历序列中数值的迭代器类型, 以及一些能够创建这些迭代器的成员函数
*/
namespace std
{
    template <typename T,>
    class vector
    {
    public:
        using const_iterator =; // implementation-specific iterator// type for constantvectors
            const_iterator begin() const; // iterator for start of collection
            const_iterator end() const; // iterator for end of collection};
    template <typename T,>
    class list
    {
    public:
        using const_iterator =; // implementation-specific iterator// type for constant lists
        const_iterator begin() const; // iterator for start of collection
        const_iterator end() const; // iterator for end of collection};
}

template <typename T>
void printMax(T const &coll)
{
    // compute position of maximum value
    auto pos = std::max_element(coll.begin(), coll.end());
    // print value of maximum element of coll (if any):
    if (pos != coll.end())
    {
        std::cout << *pos << ’ \n’;
    }
    else
    {
        std::cout << "empty" << ’ \n’;
    }
}
int   main()
{
    std::vector<MyClass> c1;
    std::list<MyClass> c2;printMax(c1);
    printMax(c2);
}

通过用这些迭代器来参数化其操作, STL 避免了相关操作在定义上的爆炸式增长。

我们并没有为每一种容器都把每一种操作定义一遍, 而是只为一个算法进行一次定义, 然后将其用于所有的容器。

泛型的关键是迭代器, 它由容器提供并被算法使用。 这样之所以可行, 是因为
迭代器提供了特定的、 可以被算法使用的接口。 这些接口通常被称为 concept, 它代表了为
了融入该框架, 模板必须满足的一组限制条件。 此外, 该概念还可用于其它一些操作和数据
结构

泛型编程之所以实用, 正是因为它依赖于静态多态, 这样就可以在编译期间就决定具体的接口。

另一方面, 需要在编译期间解析出接口的这一要求, 又催生出了一些与面向对象设计原
则(object oriented principles) 不同的新原则。

七、总结

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

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

相关文章

【C语言】内存函数介绍

它们所在的头文件&#xff1a; &#xff08;这里出现的arr都为char类型数组&#xff09;strlen作用&#xff1a;计算一个字符串的长度本质&#xff1a;历经千辛找一个 \0 &#xff0c;找到 \0 就立马停止。&#xff08;就是找 \0 &#xff09;易错&#xff1a;strlen 返回值为 …

物联网无线通信技术中蓝牙和WIFI有哪些区别?

在物联网快速发展的时代&#xff0c;联网运行的设备越来越多&#xff0c;无线通信技术在物联网中发挥着举足轻重的作用&#xff0c;无线通信技术的发展改变了信息传输的方式&#xff0c;人们在任何时间、任何地点都可以访问设备&#xff0c;目前最常用的两种无线通信技术分别是…

云服务器CentOS前后端部署流程记录

部署流程记录 购买云服务ecs服务器&#xff0c;建立CentOS系统 通过xftpxshell访问远程服务 doker部署&#xff08;https://www.runoob.com/docker/centos-docker-install.html&#xff09; docker docker部署环境&#xff08;mysql&#xff09; docker常用命令 1. docker i…

【Linux】进程状态与优先级

文章目录进程状态概念Linux中的进程状态R(running)状态S(sleeping)状态D(disk sleep)状态T(stopped)状态t(tracing stop)状态X(dead)状态Z(zombie)状态特殊的孤儿进程进程优先级进程性质补充进程状态概念 《现代操作系统》中给出的进程状态的定义如下&#xff1a; 进程状态反映…

Qt+C++窗体界面中英文多语言切换

程序示例精选 QtC窗体界面中英文语言切换 如需安装运行环境或远程调试&#xff0c;见文章底部个人微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC窗体界面中英文语言切换>>编写代码&#xff0c;代码整洁&#xff0c;规则&#x…

【Linux】软件包管理器 yum

目录 一、什么是软件包 二、如何进行软件安装 1、yum 的使用 2、yum 配置 一、什么是软件包 在Linux下安装软件&#xff0c;一个通常的办法是下载到程序的源代码&#xff0c;并进行编译&#xff0c;得到可执行程序。但是这样太麻烦了&#xff0c;于是有些人把一些常用的软…

InnoDB数据存储结构

InnoDB数据存储结构 本专栏学习内容来自尚硅谷宋红康老师的视频 有兴趣的小伙伴可以点击视频地址观看 1. 数据库的存储结构&#xff1a;页 索引结构给我们提供了高效的索引方式&#xff0c;不过索引信息以及数据记录都是保存在文件上的&#xff0c;确切来说是存储在页结构中。…

不讨论颜色的前提下,如何证明自己不是色盲?神奇的零知识证明

0x01 一个小故事 《阿里巴巴与四十大盗》中有这样一段小故事&#xff1a; 阿里巴巴会芝麻开门的咒语&#xff0c;强盗向他拷问打开山洞石门的咒语&#xff0c;他不想让人听到咒语&#xff0c;又要向强盗证明他知道这个咒语。 那应该怎么办呢&#xff1f; 便对强盗说&#xf…

基于KVM安装部署RHCOS操作系统

参考&#xff1a;Openshift 4.4 静态 IP 离线安装系列&#xff1a;初始安装 - 米开朗基杨 - 博客园 一、Openshift OCP集群安装架构示意图 RHCOS 的默认用户是 core 如果安装有问题会进入 emergency shell&#xff0c;检查网络、域名解析是否正常&#xff0c;如果正常一般是以…

重修JAVA

程序员的差距是在构思上&#xff1a;思想决定了深度&#xff0c;思想的精髓高深是很多人学不来的&#xff01; 每一门语言都有它的特点&#xff0c;有优势也有劣势&#xff0c; 所以不必拘泥于招式&#xff0c;掌握底层原理即可&#xff01; 每一们语言实际上都是一个“工具”&…

如何在您的香港主机帐户上注册多个域名

注册多个域名非常普遍。事实上&#xff0c;香港主机服务提供商鼓励这样做&#xff0c;因为它既有意义又是必要的。下面将介绍决定为什么您可能需要在香港主机上注册多个域名的几个因素。注册多个域名的原因是什么?方便多个项目如果香港主机帐户的所有者在网络上有多个不同域名…

优化vue项目后, 启动编译项目过程中 报 javaScript heap out of memory 错误 及 nodejs内存溢出

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 1、优化vue项目后&#xff0c;运行npm run serve 启动编译项目过程中 报 javaScript heap out of memory 错误 2、项目启动时&#xff0c;出现 nodejs 内存溢出错误 问题描述 提示&#xff1a;遇到…

分布式事务的背景和解决方案

在常用的关系型数据库&#xff0c;都是具备事务特性的。 那什么是事务呢&#xff1f;事务是数据库运行的一个逻辑工作单元&#xff0c;在这个工作单元内的一系列SQL命令具有原子性操作的特点&#xff0c;也就是说这一系列SQL指令要么全部执行成功&#xff0c;要么全部回滚不执…

经典算法之深度优先搜索(DFS)

&#x1f451;专栏内容&#xff1a;算法学习笔记⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐。 目录一、前言二、基本概念1.简单介绍2. 官方概念三、动图分析四、模板框架五、例题分析组合问题题干描述&#xff1a;思路…

leetcode146. LRU 缓存【python3哈希表+双向链表】利用OrderedDict以及自实现双向链表

题目&#xff1a; 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。实现LRUCache类&#xff1a; LRUCache(int capacity) 以正整数作为容量capacity初始化 LRU 缓存int get(int key) 如果关键字key存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否则…

【论文速递】9位院士Science88页长文:人工智能的进展、挑战与未来

【论文速递】9位院士Science88页长文&#xff1a;人工智能的进展、挑战与未来 【论文原文】&#xff1a;Intelligent Computing: The Latest Advances, Challenges and Future 获取地址&#xff1a;https://spj.science.org/doi/10.34133/icomputing.0006摘要&#xff1a; ​…

【阶段三】Python机器学习15篇:机器学习项目实战:支持向量机回归模型

本篇的思维导图: 项目实战(支持向量机回归模型) 项目背景 股票投资(Stock Investment)是指企业或个人用积累起来的货币购买股票,借以获得收益的行为。股票投资的收益是由“收入收益”和“资本利得”两部分构成的。收入收益是指股票投资者以股东身份,按照持股的份…

大网规划部署刷题讲解(带答案)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.大网规划刷题 前言 本章将会讲解大网规划刷题的讲解。 一.大网规划刷题 …

ArcGIS基础实验操作100例--实验85创建线要素间的最近垂线

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验85 创建线要素间的最近垂线 目录 一、实验背景 二、实验数据 三、实验步骤 &#xf…

【一文速通】数据分布不同解决办法

1. 构造合适的验证集当出现训练集和测试集分布不一致的&#xff0c;我们可以试图去构建跟测试集分布近似相同的验证集&#xff0c;保证线下验证跟线上测试分数不会抖动&#xff0c;这样我们就能得到稳定的benchmark。Qiuyan918在基于对抗验证的基础上&#xff0c;提出了三种构造…