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

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

 class Widget {
public:
Widget();
virtual ~Widget(); virtual std::size_t size() const;
virtual void normalize(); void swap(Widget& other); // see Item 25 ... };

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

 void doProcessing(Widget& w)

 {

 if (w.size() >  && w != someNastyWidget) {

 Widget temp(w);

 temp.normalize();

 temp.swap(w);

 }

 }

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

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

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

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

 template<typename T>

 void doProcessing(T& w)

 {

 if (w.size() >  && w != someNastyWidget) {

 T temp(w);

 temp.normalize();

 temp.swap(w);

 }

 }

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

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

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

3.1 显示接口的特点

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

 class Widget {
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
};

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

3.2 隐式接口的特点

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

 template<typename T>
void doProcessing(T& w)
{
if (w.size() > && w != someNastyWidget) {
...

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&&进行重载的可能性,这样就将上面的表达式的意思从一个连接词转换成了其它的意义迥然的东西。)

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

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

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

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

4. 总结

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

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

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

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:class 和 templates 都支持接口和多态. 对 classes 而言接口是 ...

  2. 读书笔记_Effective_C++_条款四十一:了解隐式接口和编译期多态

    从本条款开始,就进入了全书的第七部分:模板与泛型编程.模板与泛型在C++中是非常重要的部分,还记得本书第一章时,把C++视为一个联邦,它由四个州政府组成,其中一个政府就是模板与泛型了. 本条款是一个介 ...

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

    classes和templates都支持接口(interface)和多态(polymorphism). 对classes而言接口是显式的(explicit),以函数签名为中心.多态则是通过virtua ...

  4. [EffectiveC++]item41:了解隐式接口和编译期多态

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

    最近北京房价蹭蹭猛涨,买了房子的人心花怒放,没买的人心惊肉跳,咬牙切齿,楼主作为北漂无房一族,着实又亚历山大了一把,这些天晚上睡觉总是很难入睡,即使入睡,也是浮梦连篇,即使亚历山大,对C++的热情和追 ...

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

    1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨 ...

  7. 读书笔记 effective c++ Item 42 理解typename的两种意义

    1. class和typename意义相同的例子 问题:在下面的模板声明中class和typename的区别是什么? template<class T> class Widget; // ...

  8. 读书笔记 effective c++ Item 42 理解typename的两种涵义

    1. class和typename含义相同的例子 问题:在下面的模板声明中class和typename的区别是什么? template<class T> class Widget; // ...

  9. 读书笔记 effective c++ Item 19 像设计类型(type)一样设计

    1. 你需要重视类的设计 c++同其他面向对象编程语言一样,定义了一个新的类就相当于定义了一个新的类型(type),因此作为一个c++开发人员,大量时间会被花费在扩张你的类型系统上面.这意味着你不仅仅 ...

随机推荐

  1. C语言 memcpy二维数组的复制

    今天在实现二维数组的复制功能时,竟然出现了好多问题,还是太不小心了. 我们知道,平时进行矩阵复制,无非是二重循环进行赋值操作,所以今天想改用利用memcpy进行复制操作,当然一维数组的复制在上一篇文章 ...

  2. C# 基础控制台程序的创建,输出,输入,定义变量,变量赋值,值覆盖,值拼接,值打印

    基础学习内容有 Console.WriteLine("要输出的内容");//往外输出内容的 Console.ReadLine(); //等待用户输入,按回车键结束,防止程序闪退 控 ...

  3. java生成二维码

    具体代码如下,作为一个新手,期待与你一起交流: import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.Buf ...

  4. [翻译]现代java开发指南 第二部分

    现代java开发指南 第二部分 第二部分:部署.监控 & 管理,性能分析和基准测试 第一部分,第二部分 =================== 欢迎来到现代 Java 开发指南第二部分.在第一 ...

  5. ASP.NET通用权限框架 权限管理系统源码jquery 精美UI源码

    软件技术开发,合作请联系QQ:858-048-581 开发工具 VS2010 .sql2005.2008等(在Sql server数据执行脚本即可)  VS2010 打开保证本地运行成功(数据库.源代 ...

  6. Linux Tomcat安装,Linux配置Tomcat,Linux Tomcat修改内存,Linux tomcat修改端口

    Linux Tomcat安装,Linux配置Tomcat,Linux Tomcat修改内存,Linux tomcat修改端口 >>>>>>>>>& ...

  7. angularjs+ionic注册页面表单验证(手机号、确认密码、60s后重发验证码)

    在已建立tabs和路由的注册页面html: 功能: 进行了手机号.密码格式验证,两次密码输入是否相同的判断,都正确且复选框勾选后才可点击注册,进入tabs.mypage页面. 未进行验证码真正发送.获 ...

  8. Struts2之访问路径

    上一篇已经和大家分享了关于Struts2命名空间和Action的三种创建方式,本篇我们接着命名空间的内容,来一起探讨一下关于Struts2的访问路径问题,何为访问路径,就是指当我们在浏览器输入地址,点 ...

  9. c#基础——for循环嵌套经典练习题(打★)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 作业题1 ...

  10. Java设计模式之《观察者模式》及应用场景

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6513651.html 观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监 ...