条款41 了解隐式接口和编译期多态
此处w要满足:支持size()
normalize()
swap()
和拷贝等等隐式接口;
凡涉及 w 的任何函数调用,都可能造成模板具现化。该具现行为发生在编译期,编译期多态。
隐式接口:w不需要支持operator!=,因为可以:operator!=接受一个类型为X的对象和一个类型为Y的对象,w可以转换成X,someNastyWidget可以转换为Y。
- 只要表达式有效,template能够在编译期成功具现化,那么就实现了编译期多态。
条款42 了解typename的双重含义
- template 内出现的名称如果依赖于某个 template 参数,称之为从属名称
- 如果从属名称在 class 内呈嵌套状,那么称为嵌套从属名称
如果解析器在 template 中遇到嵌套从属名称,它便假设这个名称不是一个类型,除非手动告知它 所以需要typename 关键字对其后的名称进行声明 声明它是一个类型
template <typename C>
void f(const C& container, //不允许使用typename
typename C::iterator iter); //必须使用typename
此处,C 并不是嵌套从属名称(它并非嵌套与任何”取决于template参数”的东西内);但是 C::iterator
是个嵌套从属类型名称,所以必须要 typename 前导。
- 不可以在继承处 base class lists 或者初始化处 member initialization list 的地方使用 typename 修饰
条款43 学习处理模板化基类内的名称
template 继承的类可能无法调用基类的函数(拒绝调用),因为它知道 base class templates 可能被特化,而那个特化版本可能不提供和一般 template 相同的接口。因此往往拒绝在模板化基类内寻找继承而来的名称。
有三种解决方法:
- 在 base class 函数调用前加上”this->”
- 使用 using 声明式;此处是解决编译器不进入 base class 作用域内查找的问题,而不是继承导致的名称遮盖
- 明确调用 BaseClass::function(arg…);但是如果函数是 virtual 的,会导致关闭”virtual绑定行为”,没有走虚机制
条款44 将与参数无关的代码抽离templates
条款45 运用成员函数模板接受所有兼容类型
- 真实指针(row pointer)支持隐式转换:derived class 指针可以转换成 base class 指针;指向 non-const 对象的指针可以转换成指向const对象的指针
但是对于我们自定义的智能指针却办不到这一点。
如果针对每一个需要隐式类型转换处都对构造函数进行修改、增补,那么将无穷无尽,因为一个template可以被无限量具现化,以致生成无限量函数。
所以我们需要为这个类写一个构造模板(member function templates)
对任何类型T和任何类型U,这里可以根据 SmartPtr 生成一个 SmartPtr
explicit
,因为原始指针类型之间的转换(如从derived class指针转换为base class指针)是隐式转换。
- 声明泛化拷贝构造和 assignment 构造函数之后,还是需要声明正常的 copy 构造和 copy assignment 操作符
条款46 需要类型转换时请为模板定义非成员函数
在一个 class template 的内部,template 名称可被用来作为“template和其参数”的简略表达方式,所以在Rational
- template 实参推导过程中不会考虑隐式类型转换
所以当辨析一个 class template ,它提供的”与此 template 相关的”函数支持”所有参数的隐式类型转换”时,将那些函数定义为class template内部的friend函数。而且只能在内部定义,而不能在外部定义!
e.g.
template<typename T>
class Rational{
public:
...
friend
const Rational operator*(const Rational& lhs,
const Rational& rhs);
};
template<typename T>
const Rational<T>operator*(const Rational<T>& lhs,
const Rational<T>& rhs)
HINT:
该代码可以通过编译,而且支持混合式调用,因为当对象被声明为一个 Rational 时,class Rational 便被具现化出来,而作为过程的一部分,friend 函数也被声明出来,此时它便是一个函数,而非函数模板。因此编译器在调用的时候支持隐式类型转换。
但是无法通过链接,因为编译器知道我们调用”接受两个 Rational 参数”的函数,但是它只声明在 Rational 内,并没有被定义出来。因为类内声明的那个函数在类具现化的时候就因为模板参数被推导出来而具现化了,而类外定义的那个在类具现化的时候不为所动,所以毫无关联。无法链接。
所以此处一定要将friend函数定义于类内。
条款47 请使用traits classes表现类型信息
advance()
用于将迭代器移动给定距离:
template<typename IterT,typename DistT>
void advance(IterT& iter, DistT d);
只有支持 random access (随机访问)的迭代器才支持+=操作。对于其他迭代器,advance()
需要反复执行++ 或 — 多次。
迭代器有以下几种类型:
如果要针对不同类型的迭代器,实现不同的advance操作,就需要判断迭代器分类。
traits
:允许在编译期取得类型信息;他不是一个C++构件,而是一种技术。
iterator_traits 的运作方式:针对每一个类型的 IterT,在 struct iterator_traits 内声明某个 typedef 名为iterator_category,用来确定 IterT 的迭代器分类。
e.g.
template<...>
class deque{
public:
class iterator{
public:
typedef random_access_iterator_tag iterator_category;
...
};
...
};
template<...>
class list{
public:
class iterator{
public:
typedef bidirectional_iterator_tag iterator_category;
...
};
...
};
至于 iterator_traits:
template<typename IterT>
struct iterator_traits{
typedef typename IterT::iterator_category iterator_category;
...
};
这对用户自定义类型行得通,但是对指针行不通,因为指针不能嵌套typedef。为了支持指针迭代器,需要提供一个偏特化版本:
template<typename IterT>
struct iterator_traits<IterT*>{
typedef random_access_iterator_tag iterator_category;
...
};
然后需要重载针对每个迭代器类型的操作
然后在advance内调用上述函数即可
- 通过重载技术可以在编译期对类型执行操作,而不在运行期;避免了可执行文件的膨胀和执行效率低下。
条款48 认识模板元编程
没讲啥…