现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34196 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

12 | 编译期多态:泛型编程和模板入门

lcm
gcd
next_permutation
minmax
min
max
find
count
reverse
sort
对代码的复用
适用于不同类型的同构算法
选择形状后绘制
运行时行为变化
针对类模板和函数模板进行特化
针对类型进行特化
对函数模板进行重载
添加代码支持所需操作
显式实例化和外部实例化
实例化失败
类型检查
类模板和函数模板
整数类型的模板
求最大公约数的辗转相除法
在尾部插入数据
取得容器大小
遍历容器
push_back成员函数
size成员函数
begin和end成员函数
不同对象继承体系的代码
灵活约定
动态类型语言
多态实现
实现类
接口抽象
draw函数
Herb Sutter
鸭子类型
Duck typing
维基百科
Wikipedia
不同语言支持多态的原因
C++支持的多态形式
与动态多态的比较
模板、泛型编程和静态多态
C++标准算法
静态多态或泛型
动态多态
特化模板
实例化模板
定义模板
泛型编程
容器类特点
鸭子类型
面向对象设计
shape类
参考资料
课后思考
内容小结
“动态”多态和“静态”多态的对比
C++模板
容器类的共性
面向对象和多态
编译期多态:泛型编程和模板入门

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
相信你对多态这个面向对象的特性应该是很熟悉了。我们今天来讲一个非常 C++ 的话题,编译期多态及其相关的 C++ 概念。

面向对象和多态

在面向对象的开发里,最基本的一个特性就是“多态” [1]——用相同的代码得到不同结果。以我们在[第 1 讲] 提到过的 shape 类为例,它可能会定义一些通用的功能,然后在子类里进行实现或覆盖:
class shape {
public:
virtual void draw(const position&) = 0;
};
上面的类定义意味着所有的子类必须实现 draw 函数,所以可以认为 shape 是定义了一个接口(按 Java 的概念)。在面向对象的设计里,接口抽象了一些基本的行为,实现类里则去具体实现这些功能。当我们有着接口类的指针或引用时,我们实际可以唤起具体的实现类里的逻辑。比如,在一个绘图程序里,我们可以在用户选择一种形状时,把形状赋给一个 shape 的(智能)指针,在用户点击绘图区域时,执行 draw 操作。根据指针指向的形状不同,实际绘制出的可能是圆,可能是三角形,也可能是其他形状。
但这种面向对象的方式,并不是唯一一种实现多态的方式。在很多动态类型语言里,有所谓的“鸭子”类型 [2]
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了C++中的编译期多态和模板的应用。首先讨论了面向对象编程中的多态特性和动态类型语言中的“鸭子”类型。然后详细讨论了C++中模板的定义、实例化和特化,以及模板的显式实例化和外部实例化。通过具体示例展示了模板在处理不同类型数据时的灵活性和通用性。文章强调了C++模板在编译期间就能捕获错误的优势,以及在实例化过程中可能出现的问题。此外,还对“动态”多态和“静态”多态进行了对比,强调了C++语言在静态类型检查和泛型编程方面的优势。总的来说,本文通过深入讲解编译期多态和C++模板的使用,展示了C++语言在静态类型检查和泛型编程方面的优势,为读者提供了对这一主题的深入了解。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(21)

  • 最新
  • 精选
  • Geek_077da0
    老师您好,看到这一讲想问一个一直想问的问题。我是一个在校学生,目前学完了c++的基本语法知识并且看了一些相关的书籍,但平时能自己动手写代码的机会只有刷leetcode的时候,想请问一下老师,在去公司实习之前,有没有什么项目适合初学者练练手的。不然感觉自己看了这么多理论终究只是在纸上谈兵。

    作者回复: 可以尝试把平时用 Python、Bash 写的小工具改成用 C++。使用现代 C++,这种处理也不会太麻烦。 还有就是小的网络应用、数据处理应用等等。Leetcode 是考算法的,对语言技巧是没啥帮助。

    2019-12-23
    13
  • Jason
    老师,在参考资料3中,针对模板重载解析的例子 // template <class T> // //(a)与 void f(T)之前相同的旧基础模板 ; template <> // //(c)显式专业化,这一次(a) void f <>(int *); template <class T> // //(b)第二个基本模板,重载(a) void f(T *); // ... int * p; f(p); 是否针对int*的全特化在(a)的后面,就是属于(a)的基础模板的全特化,在(b)后面就是(b)的全特化。所以int*全特化放置的位置不同,导致了模板裁决时,因为选取了不同的基础模板而导致了不同的结果。是这样吗?

    作者回复: 是的,这是个大坑,所以Herb建议对函数用重载就行了,不会有惊讶的行为。

    2020-01-14
    10
  • panda
    第一题通过查阅资料,c++的多态性分专用多态和通用多态,专用多态又分重载多态和强制多态,通用多态又分包含多态和参数多态。 重载多态:函数重载和操作符重载。 强制多态:使一个变量类型加以变化让其符合函数操作的要求。 如:double a = 1.0; int b = 2; double c = a + b; 此时b会强制转换成double再进行+操作。 包含多态:虚函数重构。 参数多态:课程里的函数模板。 第二题,对各语言了解太浅,需老师给出解答。

    作者回复: 思考题有些是没有固定答案的,就是希望大家去思考一下。多态的分类也有很多种。包含多态、参数多态和重载多态都是常见的提法,但强制多态这个说法我不熟,查了一下才确定有这种说法,感觉和一般的多态这个词的理解还是有点差异的…… 就这第二题而言,也是希望大家去比较学习过的其他语言。这基本上取决于语言本身在类型方面的特性。 以 Python 为例,它是动态类型的语言。所以它不会有真正的静态多态。但和静态类型的面向对象语言(如 Java)不同,它的运行期多态不需要继承。没有参数化多态初看是个缺陷,但由于 Python 的动态参数系统允许默认参数和可变参数,并没有什么参数化多态能做得到而 Python 做不到的事。

    2020-01-05
    9
  • geek
    老师,静态断言示例中: template <bool> struct compile_time_error; 此处模板参数是bool和是typename T这种有什么区别吗? 另外 compile_time_error<bool(Expr)> \ ERROR_##_Msg; \ 这里我理解在条件为真时,是不是会定义一个名字为ERROR__MSG的对象?但用nm在.o文件中没看到。 最后 (void)ERROR_##_Msg;这句有啥作用啊?去掉之前和之后,似乎没啥区别。

    作者回复: 模板参数可以是值(写出类型名)或者类型(写typename)。这个例子里参数是个bool值。 临时变量不会产生一个全局的符号,在nm里都看不到的。 (void)这行的作用是抑制编译器的变量未使用告警。这是常见的显式告诉编译器不要对变量未使用进行告警的方式。

    2021-03-06
    5
  • Jason
    老师,在c++的多态中,有没有一种方式可以通过基类的指针获取实际子类的类型呢?比如我定义的两个protobuf的类型里都有user_id的字段,在代码中为了通用(不想每个类型写一个函数),反射出来传递给外围都是pb的基类指针,因为要设置子类的成员就必须要强制类型转换,但是这种情况下又拿不到子类的实际类型去做转换。decltype(基类)得到的还是基类类型

    作者回复: decltype 得到的是编译期的类型。你需要的是运行期的类型,C++ 里挺有争议(跟异常类似)的功能——RTTI。(还是要强调一句,你应该考虑是否用虚函数可以达到你需要的功能。很多项目,如 Google 的,会禁用 RTTI。) 你可以用 dynamic_cast 来转换成你需要的指针类型,如果类型不对,会得到空指针。你也可以用 typeid 直接来获取对象的实际类型。下面的代码演示一下(需要 Boost;没有安装 Boost 请自行调整,也只是不能输出友好类名而已): #include <iostream> #include <typeinfo> #include <boost/core/demangle.hpp> using namespace std; using boost::core::demangle; class shape { public: virtual ~shape() {} }; class circle : public shape { }; int main() { shape* ptr = new circle(); auto& type = typeid(*ptr); cout << type.name() << endl; cout << demangle(type.name()) << endl; cout << boolalpha; cout << (type == typeid(shape) ? "is shape\n" : ""); cout << (type == typeid(circle) ? "is circle\n" : ""); delete ptr; } 在 GCC 下的输出: 6circle circle is circle

    2020-01-16
    3
  • 陈舸
    看到编译期多态,我以为会讲一讲利用模板做static_cast<T*>(this)->implementation()的技巧。这种方法可以在编译期决定要实际调用的函数是哪一个,就可以不用虚函数了。64位平台下对于小型的类可以节省不少空间。

    作者回复: 这个可以用 SFINAE(第 14 讲)或 if constexpr(第 15 讲)做到。

    2020-02-11
    1
  • EncodedStar
    1.C++ 支持2种吧, 静态多态和动态多态,静态多态它在编译器,通过函数重载,用算符重载的方式被调用者使用,动态多态也叫运行时多态,它可以通过虚函数和继承来实现,实现是,编译器会将进程运行过程中动态绑定 2.支持多态的语言一般是面向对象的语言,所以并非所有的都有。

    作者回复: 还不够全面。可以读读参考资料。

    2020-01-03
    2
    1
  • 总统老唐
    吴老师,学完这一课,有 3 点疑问: 1,你提到的方法一,“添加代码,让那个类型支持所需要的操作(对成员函数无效)”,这里说“对成员函数无效”是具体指的什么情况? 2,实现static_assert功能时,定义 struct 模板如下: template<bool> struct compile_time_error, 和常见的模板定义头部 template <typename T> 的格式看起来不一样,常见的这种格式中参数类型是未定的,但是compile_time_error这个模板,参数明确指定是 bool 型,这是模板的另一种形式么? 3,我尝试做了以下实验 template <typename T> T addData(T a, T b) { return a + b; } double addData(double a, double b) { return (int)a + (int)b; } template <> double addData(double a, double b) { return (int)a + (int)b; } 当我调用 addData(1.5, 2.5)时,发现调用的是针对double的重载函数,而不是模板针对double 的特化,这是为什么?

    作者回复: 1. 一般而言,不应该去修改别人的类。容易出问题。所以不能添加成员函数。 2. 模板参数可以是类型,也可以是常数表达式,包括整数类型常数、枚举、指针、引用。 3. 重载比特化优先。一般而言,函数特化是不推荐的。具体看参考资料 3。

    2019-12-24
    1
  • 转遍世界
    还有个问题: 三、针对 cl_I 进行特化:这里全局模板类特化里调用了mod,这个mod是cln::cl_I的成员函数吗?能类外直接调用?

    作者回复: mod 是 cln 名空间里的独立函数。因为有 ADL,编译器能找到它,而不需要我们写 cln::mod。

    2023-12-29归属地:江苏
  • 转遍世界
    老师我有个疑问: 二、针对 cl_I 进行重载:这里。 类的成员函数能重载全局模板函数吗,不在同一个作用域啊。

    作者回复: 对,可以的。全局函数对名空间里的对象进行重载,完全没有问题。当然,放在名空间里,用 ADL 来找到也是可以的。但修改“别人”的名空间通常不那么好。

    2023-12-29归属地:江苏
收起评论
显示
设置
留言
21
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部