EffectiveC++ 第7章 模板与泛型编程
我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的“可能比较准确”的「翻译」。
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章 模板与泛型编程的更多相关文章
- 【c++ Prime 学习笔记】第16章 模板与泛型编程
面向对象编程(OOP)和泛型编程(GP)都能处理在编写程序时类型未知的情况 OOP能处理运行时获取类型的情况 GP能处理编译期可获取类型的情况 标准库的容器.迭代器.算法都是泛型编程 编写泛型程序时独 ...
- C++ Primer 5th 第16章 模板与泛型编程
模板是C++中泛型编程的基础,一个模板就是创建一个类或者函数的蓝图或者说公式. C++模板分为函数模板和类模板. 类模板则可以是整个类是个模板,类的某个成员函数是个模板,以及类本身和成员函数分别是不同 ...
- [C++ Primer] : 第16章: 模板与泛型编程
面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况, 不同之处在于: OOP能处理类型在程序运行之前都未知的情况, 而在泛型编程中, 在编译时就能获知类型了. 函数模板 模板是C++ ...
- C++ primer 模板与泛型编程
继续浏览c++ primer 看到模板与泛型编程这章.就顺便把这几节的代码综合了下,对一个Queue队列模板的实现 贴一下代码(看完书.自己敲,忘记了哪再看下书) #include <ostre ...
- C++ 模板与泛型编程
<C++ Primer 4th>读书笔记 所谓泛型编程就是以独立于任何特定类型的方式编写代码.泛型编程与面向对象编程一样,都依赖于某种形式的多态性. 面向对象编程中的多态性在运行时应用于存 ...
- C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续]
模板与泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...
- C++ Primer 学习笔记_84_模板与泛型编程 --模板特化
模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一 ...
- C++ Primer 学习笔记_77_模板与泛型编程 --实例化
模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实 ...
- C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]
模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还能够仅仅特化push和pop成员.我们将特化push成员以复制字符数组,而且特化pop成员以释放该副本使用的内存: ...
随机推荐
- 【Linux基础】iconv命令详解(编码转换)
对于给定文件把它的内容从一种编码转换成另一种编码. iconv -f GBK -t UTF- file1 -o file2 //将GBK转换为UTF8,输出到file2.没-o那么会输出到标准输出 i ...
- 干货:Vue粒子特效(vue-particles插件)
转:https://www.jianshu.com/p/53199b842d25 image.png 图上那些类似于星座图的点和线,是由vue-particles生成的,不仅自己动,而且能与用户鼠标事 ...
- 解析Object.defineProperty的作用
对象是由多个名/值对组成的无序的集合.对象中每个属性对应任意类型的值. 定义对象可以使用构造函数或字面量的形式: 除了以上添加属性的方式,还可以使用Object.defineProperty定义新属性 ...
- Spring MVC @RequestMapping注解详解
@RequestMapping 参数说明 value:定义处理方法的请求的 URL 地址.(重点) method:定义处理方法的 http method 类型,如 GET.POST 等.(重点) pa ...
- js 获取纯web地址栏中URL传参
function GetQueryString(name) { var reg = new RegExp("(^|&)"+ name +&quo ...
- keras 的 Deeplabv3+ 实现遇到的问题
代码大佬都已经写好了,具体参考:https://github.com/bonlime/keras-deeplab-v3-plus git clone 下来以后,按照指南要训练自己的数据集,只要设置好自 ...
- python小白——进阶之路——day3天-———运算符
(1)算数运算符: + - * / // % ** (2)比较运算符: > < >= <= == != (3)赋值运算符: = += -= *= /= //= %= ** ...
- vc图像合成
本程序下载地址: 上一篇讲述了tiff格式图片拆分成多张图片, 这篇博客讲述如何把多张任意格式的图片合成为一张图片. 图像合成仍然需要借助Cximage图像库,合成函数为Mixfrom, 函数原型为: ...
- websocket作用及意义
Browser已经支持http协议,为什么还要开发一种新的WebSocket协议呢?我们知道http协议是一种单向的网络协议,在建立连接后,它只允许Browser/UA(UserAgent)向WebS ...
- PS快速祛除脸上小雀斑
首先我们要把图片放到PS软件中,然后在PS左侧工具栏中找到污点修复画笔工具(J), 配合着污点修复画笔中的修补工具一起使用,注意:模式要选择正常,属性栏中类型要选择内容识别. 下一步我们需要在图层上添 ...