读书笔记 effective c++ Item 41 理解隐式接口和编译期多态

简介: 1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态。举个例子,考虑下面的类(无意义的类), 1 class Widget { 2 public: 3 Widget(); 4 virtual ~Widget(); 5 6 virtual std::...

1. 显示接口和运行时多态

面向对象编程的世界围绕着显式接口和运行时多态。举个例子,考虑下面的类(无意义的类),

 1 class Widget {
 2 public:
 3 Widget();
 4 virtual ~Widget();
 5 
 6 virtual std::size_t size() const;
 7 virtual void normalize();
 8 
 9 void swap(Widget& other);                          // see Item 25
10 
11 ...                                                                 
12 
13 };

 

考虑下面的函数(同样没有意义),

 1 void doProcessing(Widget& w)
 2 
 3 {
 4 
 5 if (w.size() > 10 && w != someNastyWidget) {
 6 
 7 Widget temp(w);
 8 
 9 temp.normalize();
10 
11 temp.swap(w);
12 
13 }
14 
15 }

 

对于doProcessing中的w,我们可以这样说:

  • 因为w被声明为Widget类型,w必须支持Widget接口。我们可以在源码中搜寻这个接口(例如,在Widget的头文件中),以便能够确切的知道它长成什么样子,所以我将其叫做一个显式的接口(explicit interface)——可以显式的在源码中看到的接口。
  • 因为Widget中的一些成员函数是虚的,w对这些函数的调用会展示出运行时多态:w具体调用哪个函数会根据运行时w的动态类型来决定。

 

2. 隐式接口和编译期多态

模板(template)和泛型编程(generic programming)的世界从根本上发生了变化。在这个世界中,显式接口和运行时多态继续存在,但是它们不再像以前那么重要。相反,隐式接口和编译时多态被挪到了前台。为了了解这是什么样子的,我们将doProcessing从函数转换为一个函数模板,看看会发生什么:

 1 template<typename T>
 2 
 3 void doProcessing(T& w)
 4 
 5 {
 6 
 7 if (w.size() > 10 && w != someNastyWidget) {
 8 
 9 T temp(w);
10 
11 temp.normalize();
12 
13 temp.swap(w);
14 
15 }
16 
17 }

 

现在我们能对doProcessing中的w说些什么呢?

  • W必须支持的接口由模板中w需要执行的操作所决定。例如,w的类型T必须支持size,normalize和swap成员函数;拷贝构造函数(来创建temp);和不等比较(同someNastyWidget进行比较)。我们很快就能发现这也不是很精确的,但是对于现在来说足够了。重要的是,这些表达式必须是T所支持的隐式接口,它们对于模板来说必须是有效的以便能够通过编译。
  • 对于涉及到w的像operator>和operator!=这样的函数调用,可能涉及到模板的实例化来让这些调用成功。这些实例化在编译期发生。因为用不同的模板参数实例化出来的函数模板会导致不同的函数被调用,这叫做“编译时多态”。

 

3. 显示接口和隐式接口的区别

 

3.1 显示接口的特点

即使你永远不使用模板,你也应该熟悉运行时多态和编译期多态的区别,因为这同编译期决定调用哪个重载函数以及运行期决定绑定哪个虚函数是类似的。隐式和显式接口的区别对于模板来说是新的概念,然而,一个显式的接口由函数签名组成,也即是函数名字,参数类型,返回值类型等等。Widget类的公共接口,例如:

1 class Widget {
2 public:
3 Widget();
4 virtual ~Widget();
5 virtual std::size_t size() const;
6 virtual void normalize();
7 void swap(Widget& other);
8 };

 

由一个构造函数,一个析构函数,和函数size,normalize和swap以及参数类型,返回值类型和这些函数的常量性组成。(同样包含编译器生成的拷贝构造函数和拷贝赋值运算符——看Item 5)。它同样可以包含typedef和数据成员,如果你够大胆违反Item22的建议的话(将数据成员声明为private)。虽然在这个例子中没有这么做。

3.2 隐式接口的特点

一个隐式的接口会有很大的不同。它不是基于函数签名。而是由有效表达式组成。再看一下doProcessing模板开始部分的条件表达式:

1 template<typename T>
2 void doProcessing(T& w)
3 {
4 if (w.size() > 10 && w != someNastyWidget) {
5 ...

 

T(w的类型)的隐式接口看上去会有如下限制:

  • 它必须提供一个名字为size的成员函数并且返回一个整型值。
  • 它必须支持!=操作符函数,能够对两个T类型的对象进行比较。(这里,我们假设someNastWidget的类型为T。)

多亏了操作符重载,上面的两个限制都不需要满足。T必须支持一个size成员函数,值得提及的是这个函数可能继承自一个基类。但是这个成员函数没有必要返回一个整型值。甚至不需要返回一个数字类型值。如果这么说的话,它甚至不需要返回operator>定义中所需要的值。他需要的是返回一个类型X的对象,于是可以在一个类型X对象和int(因为10是int型的)型对象上调用operator>。但是Operator>没有必要带一个类型X的参数,因为它也可以带一个类型Y的参数,只要Y可以隐式的转成X就可以了。

 

类似的,T也没有必要支持operator!=,因为operator!=带一个类型X的参数和一个类型Y的参数也能接受。只要T能转成X并且someNastyWidget的类型可以转换成Y,那么函数调用就是有效的。

 

(说句题外话,这个分析没有考虑将operator&&进行重载的可能性,这样就将上面的表达式的意思从一个连接词转换成了其它的意义迥然的东西。)

 

大多数人当第一次开始考虑这种隐式转换就头疼,你不需要吃阿司匹林。隐式接口只是简单的由一些有效表达式组成。表达式本身看起来复杂,但是加在上面的限制一般来说是简单直接的。例如,考虑下面的条件表达式,

1 if (w.size() > 10 && w != someNastyWidget) ...

很难说要对函数size,operator>,operator&&或者operator!=做什么限制,但是很容易辨认出需要对整个表达式做出的限制。If声明的条件部分必须是一个boolean表达式,所以不管涉及到什么类型,也不管w.size() > 10 && w != someNastyWidget产生什么,它必须同bool是兼容的。这是模板doProcessing强加在类型参数T上的隐式接口的一部分。剩下的doProcessing所需要的接口就是对拷贝构造函数的调用,还有swap对于类型T来说必须是有效的。

 

强加在模板参数上的隐式接口同强加在类对象上的显示接口一样真实,两者都是在编译阶段检查。你不能同一个类提供的显示接口相矛盾的方式使用一个类对象(不会编译通过),你也不能随便在一个模板中尝试使用一个对象,除非这个对象支持模板需要的隐式转换(否则也不能通过编译)

4. 总结

  • 类和模板都支持接口和多态。
  • 对于类来说,接口是显示的,以函数签名为中心。多态发生在运行时,通过虚函数来实现。
  • 对于模板参数来说,接口是隐式的,基于有效表达式。模板多态通过模板实例化和函数重载来实现,它发生在编译期。


作者: HarlanC

博客地址: http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

目录
相关文章
|
8月前
|
存储 人工智能 编译器
c++--多态
上一篇文章已经介绍了c++的继承,那么这篇文章将会介绍多态。看完多态的概念,你一定会感觉脑子雾蒙蒙的,那么我们先以举一个例子,来给这朦胧大致勾勒出一个画面,在此之前,先介绍一个名词虚函数,(要注意与虚拟继承区分)重定义: 重定义(隐藏)只要求函数名相同(但要符合重载的要求,其实两者实际上就是重载);重定义下:在这种情况下,如果通过父类指针或引用调用函数,会调用父类的函数而不是子类。重定义(或称为隐藏)发生的原因是因为函数名相同但参数列表不同,导致编译器无法确定调用哪一个版本的函数。
164 0
|
11月前
|
存储 编译器 C++
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
1322 0
|
12月前
|
编译器 C++
c++中的多态
c++中的多态
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
173 2
C++入门12——详解多态1
|
存储 编译器 数据安全/隐私保护
【C++】多态
多态是面向对象编程中的重要特性,允许通过基类引用调用派生类的具体方法,实现代码的灵活性和扩展性。其核心机制包括虚函数、动态绑定及继承。通过声明虚函数并让派生类重写这些函数,可以在运行时决定具体调用哪个版本的方法。此外,多态还涉及虚函数表(vtable)的使用,其中存储了虚函数的指针,确保调用正确的实现。为了防止资源泄露,基类的析构函数应声明为虚函数。多态的底层实现涉及对象内部的虚函数表指针,指向特定于类的虚函数表,支持动态方法解析。
166 1
【C++】实现日期类相关接口(三)
【C++】实现日期类相关接口
134 5
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
240 1
【C++】实现日期类相关接口(二)
【C++】实现日期类相关接口
159 1
【C++】实现日期类相关接口(一)
【C++】实现日期类相关接口
135 1
|
存储 编译器 C++