模板
Posted 小键233
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板相关的知识,希望对你有一定的参考价值。
隐式接口与编译器多态
假如有如下的模板函数:
template<typename T>
void doSomething(T& t)
if(t.getIs() )
t.traver();
那么,对于T 而言,它的隐式接口便是:
bool getIs();
traver();
T 的类型必须要支持这两个接口才能通过编译。
而编译器多态是:
以不同的template 参数具现化function templates ,会导致调用不同的函数
关于typename 的含义
在template 的声明式中,经常有看到class 代替typename 的情况
template<class T> SomeClass;
template<typename T> SomeClass;
其实这是因为历史原因,一开始是让用class 的,但是后来为了把class 归化到类的声明中,而引入了新的关键字typename。
除了多打了几个字符,它们并没有什么分别。
然而,typename 存在的意义并不只是做一点原来class 能做的事情。
考虑有这样的代码:
template<typename T>
void doSomething(T& t)
T::some_type * x; //1
//...
现在问题来了,怎么解释注释1 的那句。
如果some_type 是一种类型,那么x 就是一个指针。
如果some_type 是一个变量(T 中的静态变量,或者其他),那么这句就变成了some_type 乘以x了。
那么在C++中,有个规则解析这一中歧义的状态:
如果解析器在template 中遭遇一个嵌套从属名称,它便假设这名称不是个类型,除非你告诉它是。[1]
所以,对于上述的语句,会被解释为some_type 乘以x。
如果想声明的话,那么应该在嵌套从属名称之前放置typename:
template<typename T>
void doSomething(T& t)
typename T::some_type * x; //1
//...
它告诉编译器,这是个类型!这就是typename 的第二个含义了。
但是“typename 必须作为嵌套从属类型名称的前缀词”这一规则有个例外:
typename 不可以出现在base classed list 内嵌套从属类型名称之前,也不可在member initialization 中作为 base class 修饰词。
比如:
template<T>
class SomeClass: Base<T>::Nested
public:
SomeClass(int x): Base<T>::Nested(x)
...
;
模板类成为基类时
看下面的代码
class Base
public:
void doSomething()
virtual ~Base()
;
class Derived: public Base
public:
void func()
doSomething();
;
如果有使用:
Derived d;
d.func();
ok。这个表现得很好,程序能够编译和运行。
现在我们要给它们加上模板了。
template<typename T>
class Base
public:
virtual ~Base()
void doSomething()
;
template<typename T>
class Derived: public Base<T>
public:
void func()
doSomething();
;
除了加上模板,并没有什么不同的。
但是,当这样的时候:
Derived<int> d;
d.func();
ok,它挂了,它报错了,它变了,它以前不是这样的!
为什么呢?
在回答这个问题之前,我们不妨给Base模板类写个全特化的版本:
struct Special
;
template<>
class Base<Special>
public:
virtual ~Base()
//Special 的特化版本中没有dosomething 的函数
;
这完全没有问题,也能通过编译,这时候,假如有下面的使用:
Derived<int> d; //这个有api doSomething
Derived<Special> ds;//这个没有api
看明白了吗,如果模板类的Base 特化以后,它的行为可能会和泛化的完全不一样,那么在继承了模板类的Derived Class 中,不问结果地调用基类的接口,可能会出错。
这就是为什么编译出错的原因:
它知道base template class 有可能会特化,而那个特化版本可能不提供和一般性的template 相同的接口。因此,它往往拒绝在templatized base class (模板化基类)内寻找继承而来的名称。[1]
直白点,就是说,当继承模板化基类时,它的派生类会假装我并不知道基类的接口是什么
如果我确实是想调用一般化的基类的接口,那么有两种方法
- 加this 指针调用
- 使用using
对于代码如下:
template<typename T>
class Derived: public Base<T>
public:
void func()
using Base<T>::doSomething(); //两种选一种就可以了
this->doSomething();
;
模板类型转换
假设我们现在写一个封装类型的类:
template<typename T>
class Type
T i;
public:
Type(T ini) : i(ini)
Type doOpe (const Type& b) const
return i*b.i;
;
template<typename T>
Type<T> operator * (const Type<T>& a, const Type<T>& b)
return a.doOpe(b);
我们希望能够做乘法运算,于是重载了操作符。
那么,假如现在这么使用:
Type<int> a(3);
Type<int> b(2);
Type<int> c = a*b;
Type<int> d = a*4; //不通过
你会发现其中d 不会被编译通过。
但是在使用的过程中,我们是希望这句能够通过的,因为我们将int 封装为Type ,自然希望这样的乘法能够通过。
不能通过是因为:
template实参推导过程中从不将隐式类型转换函数纳入考虑。[1]
也就是说,必须这样显示地使用才会让你通过编译:
Type<int> d = ::operator*<int>(a,4);
但是,这样使用未免不够直观,所以,我们可以把operator × 操作符声明为Type 的友元函数。这样,当具现化Type 时,友元函数自然也会被推导出来,于是编译器找到合适的函数,运行下去:
template<typename T>
class Type
T i;
public:
Type(T ini) : i(ini)
friend Type operator * (const Type& a, const Type& b)
return a.i*b.i;
;
*有些编译器强制要求模板类的定义和实现放在一起。
事已至此,皆大欢喜。
[参考资料]
[1] Scott Meyers 著, 侯捷译. Effective C++ 中文版: 改善程序技术与设计思维的 55 个有效做法[M]. 电子工业出版社, 2011.
(条款41:了解隐式接口和编译器多态;
条款42:了解typename 的双重含义;
条款43:学习处理模板化基类内的名称;
条款46:需要类型转换时请为模板定义非成员函数)
以上是关于模板的主要内容,如果未能解决你的问题,请参考以下文章