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

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. 总结

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

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

Effective C++ Item 41 了解隐式接口和编译期多态

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:class 和 templates 都支持接口和多态. 对 classes 而言接口是显式的,以函数签名为中心.多态则是通过 virtual 函数发生于运行期 对 templates 参数而言,接口是隐式的,奠基于有效表达式.多态则是通过 templates 具体化和函数重载解析(function overloading resolution)发生于编译期. 示例1:显式接口和运行期

Effective C++ 条款41 了解隐式接口和编译期多态

1. 面向对象编程通常以显式接口(类中的函数原型)和运行时多态(虚函数和RTTI)解决问题,但在Templates及泛型编程的世界,尽管显式接口和运行时多态仍然存在,但隐式接口和编译时多态的重要性却明显提升. 2.通常显式接口由函数签名式构成,而隐式借口并不基于函数声明式,而是有有效表达式组成. 所谓泛型编程中的隐式接口,指的是类型参数在函数模板或类模板中的成员函数内部所涉及的操作.也就是说类型参数必须支持函数模板内部所进行的操作,比如operator>,operator!=等,而对于不同类型,

条款41:了解隐式接口和编译期多态

1.显式接口和运行期多态 (1)简介 面向对象编程总是以显式接口和运行期多态来解决问题.例如: class Widget{ public: Widget(); virtual ~Widget(); virtual std::size_t size() const; virtual void normalize(); virtual swap(Widget& other); }; void doProcessing(Widget& w) { if (w.size() > 10 &

Effective C++ -----条款41:了解隐式接口和编译期多态

classes和templates都支持接口(interface)和多态(polymorphism). 对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtual函数发生于运行期. 对template参数而言,接口是隐式的(implicit),奠基于有效表达式.多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期.

读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)

最近北京房价蹭蹭猛涨,买了房子的人心花怒放,没买的人心惊肉跳,咬牙切齿,楼主作为北漂无房一族,着实又亚历山大了一把,这些天晚上睡觉总是很难入睡,即使入睡,也是浮梦连篇,即使亚历山大,对C++的热情和追求还是不减,应该是感动了周公吧,梦境从此处开始,大师入场来给我安慰了... 11点躺在床上了,脑子里总结一下最近的工作:最近的开发用到inline函数比较多,众所周知,inline的使用是为了提高程序性能,可结果却总不尽如人意,这个捉急啊,嗯?怎么突然到了山脚下,周边树木林立,郁郁葱葱,鸟儿委婉啼叫

读书笔记 effective c++ Item 49 理解new-handler的行为

1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨论推迟到本条款结束的时候. 1.1 调用set_new_handler来指定全局new-handler 在operator new由于不能满足内存分配要求而抛出异常之前,它会调用一个客户指定的叫做new-handler的错误处理函数.(这也不是完全正确的.Operator new的真正行为更加复杂.

读书笔记 effective c++ Item 35 考虑虚函数的替代者

1. 突破思维——不要将思维限定在面向对象方法上 你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系.你的游戏处在农耕时代,人类很容易受伤或者说健康度降低.因此你决定为其提供一个成员函数,healthValue,返回一个整型值来表明一个人物的健康度.因为不同的人物会用不同的方式来计算健康度,将healthValue声明为虚函数看上去是一个比较明显的设计方式: 1 class GameCharacter { 2 public: 3 4 virtual int healthValue()

读书笔记 effective c++ Item 55 让你自己熟悉Boost

你正在寻找一个高质量的,开源的,与平台和编译器无关的程序库的集合?看一下Boost吧.想加入一个由雄心勃勃的,充满天赋的正致力于最高水平的程序库设计和实现工作的C++程序员们组成的团体么?看一下Boost吧.想了解C++将来可能会是什么样子的?看一下Boost吧. Boost是一个C++开发人员组成的团体,也是供免费下载的C++程序库的集合.网址是http://boost.org. 1. Boost的两大优势 当然,有许多C++组织和网站,但是Boost有两点是其它组织不能与之媲美的.首先,它和

读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库

1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C++标准预计在2009年被发布(虽然所有的工作很有可能在2007年底被完成).直到现在,发布下一版C++的预计年份还没有被确定,这就解释了为什么人们把下一版C++叫做“C++0x”——C++的200x年版本. C++0x可能会包含一些有趣的新的语言特性,但是大多数新C++功能将会以标准库附加物的形式被