我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的“可能比较准确”的「翻译」。

Chapter 7 模版与泛型编程

Templates and Generic Programming

本章无法使你成为一个专家级的template程序员,但可以使你成为一个比较好的template程序员。本章也会给你必要信息,使你能够扩展你的template编程,到达你所渴望的境界。


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

在oop的世界里,我们总是以显式接口(explicit interfaces)和运行期多态(runtime polymorphism)解决问题。举个例子,给定这样(没啥意义)的class:

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

和这样的函数(也没啥意义):

void doProcessing(Widget& w)
{
if(w.size()>10 && w != someNastyWidget){
Widget temp(w);
temp.normalize();
temp.swap(w);
}
}

我们可以这么理解此函数内的 w :

  • 由于w的类型是Widget,所以w必须支持Widget接口。我们可以在例如Widget.h中的源代码找出这个接口,看看是什么样子,此时称它为显式接口,也就是它在源码中明确可见。

  • 由于Widget的某些成员函数是virtual,w对那些函数的调用将表现出运行期多态,也就是会在运行期间根据w的动态类型(条款37)调用匹配的函数。

但Template以及泛型编程的世界,与oop有根本上不同。在此世界中显式接口和运行期多态存在,但重要性降低。反倒是隐式接口(implicit interfaces)和编译器多态(compile-time polymorphism)相当重要。看看例子:

template<typename T>	//doProcessing函数模版
void doProcessing(T& w)
{
if(w.size()>10 && w != someNastyWidget){
T temp(w);
temp.normalize();
temp.swap(w);
}
}

现在如何理解doProcessing内的w呢?

  • w必须支持哪种接口,系由template中对w的操作而定。本例看来w的类型T似乎必须支持size、normalize和swap成员函数、copy构造函数、不等比较。后面我们会知道这并非完全正确,但对目前而言足够真实。重要的是,这一组表达式便是T必须支持的一组隐式接口。

  • 凡设计w的任何函数调用,例如operator>和operator!=,有可能造成template具现化,使这些调用成功。具现行为发生在编译期。”以不同的template参数具现化function templates”会导致调用不同函数,这便是所谓编译期多态。

你应该不陌生“运行期多态”和“编译期多态”之间的差异,因为它类似于“哪个重载函数该被调用(发生在编译期)”和“哪个virtual函数该被绑定(运行期)”之间的差异。而显式接口和隐式接口的差异比较新颖

通常显式接口由函数的签名式(函数名、参数类型、返回类型)构成

例如Widget class:

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

它的public接口由一个构造函数、一个析构函数、函数size、normalize、swap及其参数类型、返回类型、常量性构成。当然也包括编译器产生的拷贝构造函数和拷贝赋值(copy assignment)操作符。另外也可包括typedefs,甚至是你强制声明的public成员变量

隐式接口则并不基于函数签名式,而是由有效表达式(valid expressions)组成。

再看看doProcessing template一开始的条件:

template<typename T>

void doProcessing(T& w)

{

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

....

T(w的类型)的隐式接口看来似乎有这些约束:

  • 它必须提供一个名为size的成员函数,此函数返回一个int
  • 它必须支持一个 operator!= 函数,用来比较两个T对象。这里我们假设someNastyWidget的类型为T

由于operator overloading带来的可能性,这两个约束都不需要满足。T必须支持size函数,但此函数可能从base class继承而来,这个成员函数不需返回一个int、甚至不需返回数值。由此来看,它甚至不需返回一个定义有operator>的类对象或其引用!它唯一需要做的是返回一个类型为X的对象,而X对象与一个int(也就是10)必须能调用一个operator>。这个operator>不需要非得取一个类型为X的参数不可,因它也可以取得类型Y的参数,只要存在一个隐式转换能将X对象转换为类型Y对象。

同理,T不需支持operator!=,因为以下这样也是可以的:

operator!= 接受一个类型为X的对象和一个类型为Y的对象,T可悲转换为X而someNastyWidget 的类型可被转换为Y,这样即可有效调用 operator!= 。

当我们第一次以这种方式思考隐式接口,会觉得头疼。隐式接口仅有一组有效表达式构成,表达式自身也许看起来复杂,但它们要求的约束条件一般相当直接而明确。例如以下表达式:

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

if语句的条件式必须是个bool表达式,所以无论涉及什么实际类型,无论“w.size() > 10 && someNastyWidget”导致什么,它都必须与bool兼容。这是template doProcessing加诸于其类型参数T的隐式接口的一部分。doProcessing要求的其他隐式接口:拷贝构造函数、normalize和swap也都必须对T型对象有效

请记住:

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

条款42: 了解typename的双重意义

提问:以下template声明式中,class和typename有何不同?

template<class T> class Widget

template<typename T> class Widget

答案:完全相同。只不过某些程序员因为可少打几个字选择class,其他人喜欢typename,因为其暗示参数并非一定是一个class类型。

然而C++并不总把class和typename视为等价。有时你一定得使用typename。为了了解这种情况,我们必须先谈谈你可以在template内指涉(refer to)的两种名称。

假设有一个template function,接受一个STL兼容容器为参数,容器内持有的对象可被赋值为ints。再假设此函数仅打印其第二元素的值。这是个无聊的函数,下面是实践的一种方式:

template <typename C>
void print2nd(const C& container)
{ // 注意这不是有效的c++代码
if(container.size() >= 2){
C::const_iterator iter(container.begin());
++iter; // 将迭代器移往第二个元素
int value = *iter;
std::cout << value;
}
}

现在代码中强调两个local变量iter和value。iter的类型是C::const_iterator,实际怎样取决于template参数C。template内出现的名称若相依于某template参数,称之为从属名称。若从属名称在class内呈嵌套状,我们称它为嵌套从属名称。C ::const_iterator就是这样一个名称。实际上它还是个嵌套从属类型名称,也就是个嵌套从属名称且指涉某类型。

print2nd内的另一个local变量value,类型为int。int是一个并不倚赖任何template参数的名称。这样的名称是谓非从属名称。

嵌套从属名称可能会导致解析(parsing)困难。举个例子,我们将print2nd改成这样:

template <typename C>
void print2nd(const C& container)
{
C::const_iterator* x;

}

看起来我们好像声明x为一个local变量,它是个指针,指向一个C::const_iterator。但它之所以被那么认为,只因为我们“已经知道”C ::const_iterator是一个类型。但若它不是个类型呢?假设C有个static成员变量碰巧被命名为const_iterator,或x碰巧是个global变量名称?那样的话上述代码不再是声明一个local变量,而是一个相乘动作:

C::const_iterator乘以x

在我们知道C是什么之前,没有任何办法可以知道C::const_iterator是否为一个类型。当编译器开始解析template print2nd时,尚未确知C是什么。C++有一个规则可解析此歧义状态:若解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非你告诉它是。所以默认情况下嵌套从属名称不是类型。此规则有个例外,稍后提到。

现在看看print2nd的起始处:

template <typename C>
void print2nd(const C& container)
{
if(container.size() >= 2){
C::const_iterator iter(container.begin()); //这个名称被假设为非类型
...

现在清楚为啥这不是有效的C++代码了吧。iter声明式只有在C::const_iterator是个类型时才合理,当我们并没有告诉C++说它是,于是C++假设它不是。解决办法是紧邻它之前放置关键字typename即可

template<typename C>		//这是合法的C++代码
void print2nd(const C& container)
{
if(container.size() >= 2){
typename C::const_iterator iter(container.begin());
...
}
}

一般性规则很简单:任何时候你想在template中指涉一个嵌套从属类型名称,必须在它前面放置关键字typename。(很快会谈到一个例外)

typename仅用来验明嵌套从属类型名称;其它名称不该有它存在。例如下面这个函数模版,接受一个容器和一个“指向该容器”的迭代器:

template<typename C>
void f(const C& container, // 不允许使用typename
typename C::iterator iter); // 一定要使用

这一规则的例外是,typename不可出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值列)中作为base class修饰符。例如:

template<typename C>
class Derived: public Base<T>::Nested{ // base class list中
public: // 不允许typename
explicit Derived(int x)
: Base<T>::Nested(x) // mem.init.list中
{ // 不允许typename
typename Base<T>Nested temp; // 嵌套从属类型名称
...
}
...
};

让我们看看最后一个typename例子,那是你将在真实程序中看到的代表性例子。假设我们在撰写一个function template,它接受一个迭代器,而我们打算为该迭代器指涉的对象做一份local附件temp:

template<typename IterT>
void workWithIterator(IterT iter){
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}

std:: iterator_traits::value_type是标准traits class(条款47)的一种运用,相当于说“类型为IterT之对象所指之物的类型”。这个语句声明一个local变量temp,使用IterT对象所指物的相同类型,并将temp初始化为iter所指物。比如IterT是vector ::iterator,则temp的类型就是int。

如果你觉得这个名字太长了, 便想建立一个typedef。对于traits成员名称如value_type,普通的习惯是设定typedef名称用以代表某个traits成员名称,于是常常可看到类似这样的local typedef:

template<typename IterT>
void workWithIterator(IterT iter){
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
...
}

记得在typedef里加上必要的typename关键字!

值得注意的是,某些编译器接受的代码原本有的typename却被遗漏了;原本不该有typename的出现了;有的旧版本编译器直接拒绝typename。这意味在移植性方面会带给你头疼。

请记住:

  • 声明template参数时,前缀关键字class和typename可互换
  • 请使用typename标识嵌套从属类型名称;

条款43: 学习处理模版化基类内的名称

---持续更新中

EffectiveC++ 第7章 模板与泛型编程的更多相关文章

  1. 【c++ Prime 学习笔记】第16章 模板与泛型编程

    面向对象编程(OOP)和泛型编程(GP)都能处理在编写程序时类型未知的情况 OOP能处理运行时获取类型的情况 GP能处理编译期可获取类型的情况 标准库的容器.迭代器.算法都是泛型编程 编写泛型程序时独 ...

  2. C++ Primer 5th 第16章 模板与泛型编程

    模板是C++中泛型编程的基础,一个模板就是创建一个类或者函数的蓝图或者说公式. C++模板分为函数模板和类模板. 类模板则可以是整个类是个模板,类的某个成员函数是个模板,以及类本身和成员函数分别是不同 ...

  3. [C++ Primer] : 第16章: 模板与泛型编程

    面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况, 不同之处在于: OOP能处理类型在程序运行之前都未知的情况, 而在泛型编程中, 在编译时就能获知类型了. 函数模板 模板是C++ ...

  4. C++ primer 模板与泛型编程

    继续浏览c++ primer 看到模板与泛型编程这章.就顺便把这几节的代码综合了下,对一个Queue队列模板的实现 贴一下代码(看完书.自己敲,忘记了哪再看下书) #include <ostre ...

  5. C++ 模板与泛型编程

    <C++ Primer 4th>读书笔记 所谓泛型编程就是以独立于任何特定类型的方式编写代码.泛型编程与面向对象编程一样,都依赖于某种形式的多态性. 面向对象编程中的多态性在运行时应用于存 ...

  6. C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续]

    模板与泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...

  7. C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

    模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一 ...

  8. C++ Primer 学习笔记_77_模板与泛型编程 --实例化

    模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实 ...

  9. C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

    模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还能够仅仅特化push和pop成员.我们将特化push成员以复制字符数组,而且特化pop成员以释放该副本使用的内存: ...

随机推荐

  1. 明天研究下jpa直接像django一样生成

    https://blog.csdn.net/yztezhl/article/details/79390714 自动生成 教程-- https://blog.csdn.net/mxjesse/artic ...

  2. Zookeeper集群为什么要是单数

    (原) 在zookeeper集群中,会有三种角色,leader. follower. observer分别对应着总统.议员.观察者. 半数以上投票通过:可以这样理解.客户端的增删改操作无论访问到了哪台 ...

  3. 二 Struts2 接收数据

    struts2绑定页面参数三种方式1.普通属性:在action中写与页面参数相同的属性名,然后set方法2.用对象来接收:在action中写一个对象,表单元素名改为:对象名.属性名3.用实现Model ...

  4. springboot2+freemarker简单使用

    一.src/main/resources/templates下新建welcome.ftl <!DOCTYPE html> <html lang="en"> ...

  5. mac 利用svn下载远程代码出现Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.

    终端输出的信息:Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo. ...

  6. SkylineGlobe7.0.1版本 通过鼠标左右平移模型对象

    帮同事写了一段测试代码,如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ht ...

  7. 【转】SpringBoot启动服务的三种方式

    1.IDEA启动 2.命令行启动 首先将命令行位置跳转到当前项目的根目录下,再输入“mvn spring-boot:run”命令,初次操作maven需要下载插件等待几分钟 3.命令行编译为jar启动 ...

  8. HTML/CSS 速写神器 Emmet语法

    Emmet 是高效.快速编写 HTML 和 CSS 代码的一种插件,如果还不了解,请戳Emmet — the essential toolkit for web-developers,再根据你使用的编 ...

  9. TypeError: sequence item 1: expected str instance, int found

    Error Msg Traceback (most recent call last): File "E:/code/adva_code/my_orm.py", line 108, ...

  10. 安装inotify-tools监控工具

    安装inotify-tools监控工具 yum install -y inotify-tools 2:查看inotify-tools包的工具程序 [root@dns3 ~]# rpm -ql inot ...