- 条款41:了解隐式接口和编译期多态
- 条款42:了解typename的双重意义
- 条款43:学习处理模板化基类内的名称
- 条款44:将与参数无关的代码抽离templates
- 条款45:运用成员函数模板接受所有兼容类型
- 条款46:需要类型转换时请为模板定义非成员函数
- 条款47:请使用traits classes表现类型信息
- 条款48:认识template元编程
条款41:了解隐式接口和编译期多态
Understand implicit interfaces and compile-time polymorphism.
显式接口和运行期多态
面向对象编程中,以显式接口(explicit interface)和运行期多态(runtime polymorphism)解决问题。例如:
class Widget {
public:
/* 显式接口 由函数签名式构成 */
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
...
};
void doProcessing(Widget& w)
{
if (w.size() > 10 && w != someNastyWidget) { // size是virtual函数, 运行期根据w动态类型决定
Widget temp(w);
temp.normalize(); // normalize是virtual函数, 运行期根据w的动态类型决定
temp.swap(w);
}
}
对于doProcessing参数w,所谓显式接口和运行期多态,具体是指:
- 由于w类型被声明为Widget,所以w必须支持Widget接口:Widget类对应头文件(如Widget.h)中声明的接口,称为显式接口(explicit interface);
- 由于Widget的某些成员函数是virtual,w对那些函数的调用将表现出运行期多态(runtime polymorphism),也就是说将于运行期根据w的动态类型(条款37)决定究竟调用哪个函数;
显式接口通常由函数的签名式(函数名称、参数类型、返回类型)构成,如Widget class的public接口:构造函数、析构函数、函数size,normalize,swap及其参数类型,返回类型,常量性(constness),也包括编译器合成的copy构造函数、copy assignment操作符等。
隐式接口和编译期多态
与面向对象不同,在template和泛型编程中,隐式接口(implicit interface)和编译期多态(compile-time polymorphism)更重要。
隐式接口不基于函数签名式,而是由有效表达式组成。隐式接口在编译期完成检查。具体看下面的例子,
将doProcessing改写成template版本
template<typename T>
void doProcessing(T& w)
{
if (w.size() > 10 && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}
T(w的类型)的隐式接口看起来好像有这些约束:
- 它必须提供一个名为size的成员函数,该函数返回一个整数值;
- 它必须支持一个operator!= 重载运算符的函数,用于比较2个T类型对象。
然而,由于操作符重载(operator overloading)的存在,这2个约束都不必满足。一种可能性是,T可以支持size成员函数,而size也可能从base class继承(不必是T自身的)而得到的。size也不必返回一个整数值,也可以不必是一个数值类型,唯一需要的是返回X类型的对象,X对象+int(10的类型)必须能够调用一个operator>。也就是说,X不必支持operator>,也可以是Y类型,而只需要存在一个隐式转换能将X对象转换为Y对象,而Y对象支持operator>。
类似地,T不需要支持operator!=(调用形式X.operator!=(Y)),也可以是operator!=(X,Y)(重载operator!=),其中T可以转换为X类型,someNastyWidget可以转换为Y类型。
另外,opeartor&&也可能被重载,而不再是逻辑与运算符。
函数size, operator>, opeartor&&, opeartor!= 身上的约束条件,很难明确描述,但是整体确认表达式约束条件却很容易。如if语句(if (w.size() > 10 && w != someNastyWidget))的条件必须是bool表达式。
小结
1)class和template都支持接口(interface)和多态(polymorphism)。
2)对class而言,接口是显式的(explicit),以函数签名为中心。多态则是通过virtual函数发生于运行期(通过指针要调用的函数)。
3)对template参数而言,接口是隐式的(implicit),奠基于有效表达式。多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。
[======]
条款42:了解typename的双重意义
Understand the two meaning of typename.
在声明template参数时,class和typename完全相同。
// 下面2行等价
template<class T> class Widget;
template<tyepname T> class Widget;
然而,class和typename并不总是等价。比如,
// 错误示例:嵌套从属名称,会被假设为非类型,除非使用typename指定
template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // 不会通过编译. C::const_iterator是嵌套从属名称, 编译器并不会认为这是一个类型
++iter;
int value = *iter;
cout << value;
}
}
嵌套从属名称:template内出现的名称如果依赖于某个template参数,称为从属名词(dependent name)。如果从属名词在class内呈现嵌套状,比如C::const_iterator,称为嵌套从属名词(nested dependent name)。
看起来,C::const_iterator是一个类型,但实际上编译器会默认认为它是一个成员变量。如果要让编译器认为这是一个类型,就要用typename明确指出。
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin()); // OKtypename 指明嵌套从属名C::const_iterator是个类型
...
}
指定嵌套从属类型名称的一般性规则:当你想要在template中指涉一个嵌套从属类型名称时,在紧邻它的前一个位置上放关键字typename即可。
例外情况:typename不可以出现在base class list(基类继承列表)内嵌套从属类型名称前,也不可以在构造函数的member initialization list (成员初值列表)中作为base class修复。例如,
template<typename T>
class Derived: public Base<T>::Nested { // 基类继承列表,不允许出现typename
public:
explicit Derived(int x) : Base<T>::Nested(x) { // 构造含的member initialization list,不允许出现typename
typename Base<T>::Nested temp; // 嵌套从属类型名称,需要前缀typename
...
}
};
typename另外一个用途:用typedef typename 为嵌套从属类型名定义一个简短的别名。
template<typename IterT>
void workWithIterator(IterT iter)
{
// 类型为IterT之对象所指之物的类型
typedef typename std::iterator_traits<IterT>::value_type value_type; // 定义别名
value_type temp(*iter);
...
}
小结
1)声明template参数时,前缀关键字class和typename可互换;
2)请使用关键字typename标识嵌套从属类型名称;但不得在base class list(基类列)或member initialization list(成员初值列)内以它作为base class修饰符;
3)typename相关规则在不同编译器有不同的实践;
[======]
条款43:学习处理模板化基类内的名称
Know how to access names in templatized base classes.
我们定义如下基于template的class,用于传递消息
class CompanyA {
public:
void sendClearText(const std::string& msg);
void sendEncrypted(const std::string& msg);
};
class CompanyB {
public:
void sendClearText(const std::string& msg);
void sendEncrypted(const std::string& msg);
};
class MsgInfo { ... }; // 用来保存信息
// 消息发送类
template<typename Company>
class MsgSender {
public:
void sendClear(const MsgInfo& info) { // 信息传递函数
std::string msg;
根据info参数信息;
Company c;
c.sendCleartext(msg); // OK
}
void sendSecret(const MsgInfo& info) {
}
};
// 带日志功能的消息发送类
template<typename Company>
class LogginMsgSender : public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info) { // 信息传递函数, 不与base MsgSender<Company>的同名, 避免遮掩继承而得到的non-virtual函数
将“传送前”的信息写至log;
sendClear(info); // 报错:编译器无法识别该函数
将“传送后”的信息写至log;
}
};
编译器遇到派生类template LogginMsgSender的定义式时,并不知道它继承什么样的class,因为其中的Company是个template参数,不到LogginMsgSender被具现化,无法确切知道它是什么。如果无法知道class MsgSender
对于特例化模板也是一样
class CompanyZ {
public:
void sendEncrypted(const std::string& msg);
};
// MsgSender针对CompanyZ进行的全特化
template<>
class MsgSender<CompanyZ> {
public:
void sendSecret(const MsgInfo& info) {
}
};
template<typename Company>
class LogginMsgSender : public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info) {
将“传送前”的信息写至log;
sendClear(info); // 报错:编译器无法识别该函数
将“传送后”的信息写至log;
}
};
编译器无法识别模板基类内名称解决办法
1)在模板基类函数调用动作前加上"this->"
template<typename Company>
class LogginMsgSender : public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info) {
将“传送前”的信息写至log;
this->sendClear(info); // OK
将“传送后”的信息写至log;
}
};
2)使用using声明式
见条款33,using声明式可以将”被遮掩的base class名称“带入一个derived class作用域内。不过这里并不是base class名称被derived class名称遮掩,而是编译器不进入base class作用域内查找,因为编译器不知道MsgSender
template<typename Company>
class LogginMsgSender : public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear; // 告诉编译器,请它假设sendClear位于base class内
void sendClearMsg(const MsgInfo& info) {
将“传送前”的信息写至log;
sendClear(info); // OK
将“传送后”的信息写至log;
}
};
3)明确调用base class内的函数
template<typename Company>
class LogginMsgSender : public MsgSender<Company> {
public:
void sendClearMsg(const MsgInfo& info) {
将“传送前”的信息写至log;
MsgSender<Company>::sendClear(info); // OK
将“传送后”的信息写至log;
}
};
缺点:如果被调用的是virtual函数,上面的明确调用方式会关闭“virtual绑定行为”。建议使用1)或者2)。
小结
1)可在derived class template内通过“this->”指涉base class template内的成员名称,或借由一个明白写出的“base class资格修饰符”完成。
[======]
条款44:将与参数无关的代码抽离templates
Factor parameter-independent code out of templates.
template的存在是为了节省时间和避免重复代码。
举个例子,现在你想为固定尺寸的正方形矩阵编写一个template,该矩阵支持逆矩阵运算(matrix inversion)。
// template导致代码膨胀的一个典型例子
template<typename T, std::size_t n> // template 支持nxn矩阵, 元素是类型为T的object
class SquareMatrix {
public:
void invert(); // 求矩阵的逆
};
// 客户端
SquareMatrix<double, 5> sm1;
sm1.invert(); // 调用SquareMatrix<double, 5>::invert
SquareMatrix<double, 10> sm2;
sm2.invert(); // 调用SquareMatrix<double, 10>::invert
该template会根据客户端调用情况,具现化2份invert,但除了矩阵尺寸不一样,2个函数其余部分完全相同。
改善:增加一个base class辅助求矩阵逆,将矩阵尺寸从模板参数移除,放到base class的函数参数中 -- 利用函数参数消除非类型模板参数
// 所有给定元素对象类型的矩阵, 共享同一个SquareMatrixBase
template<typename T> // 与尺寸无关的base class, 用于方阵
class SquareMatrixBase {
protected: // 为何用protected, 不用public/private? 因为SquareMatrixBase::invert只是"避免derived class代码重复"的一种方法
void invert(std::size_t matrixSize); // 以给定尺寸求逆矩阵
};
tempalte<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> { // 为什么是private继承? 不是is-a关系, 而是has-a关系, 辅助求矩阵逆
private:
using SquareMatrixBase<T>::invert; // 避免遮掩base的invert
public:
void invert() { this->invert(n); }
};
带参数的invert移到base class SquareMatrixBase中,这样就只对base class矩阵元素对象的类型 参数化,而不对矩阵尺寸参数化。因此,对于某给定元素对象类型,所有矩阵共享同一个SquareMatrixBase class。可以有效减少代码量。
为何函数SquareMatrixBase::invert是protected,而不是public或private?
因为该函数只是“避免derived class代码重复”的一种方法,客户无需知道,而又需要被derived class调用,因此应该为protected。
为何使用SquareMatrix类中使用using SquareMatrixBase
根据条款43的方法2,使用using声明式,是告诉编译器请它假设invert位于base class内。
为何SquareMatrix::invert函数还要使用“this->”记号?
因为derived class也实现了同名函数invert,如果不这样做,根据条款43,模板化基类内的函数名会被derived class遮掩。
为何SquareMatrix是private继承SquareMatrixBase,而不是public继承?
因为SquareMatrix不是SquareMatrixBase,两者并非is-a关系,SquareMatrixBase的存在只是为了帮助SquareMatrix进行求逆矩阵。
接下来的问题是,
如何存储矩阵数据?应该存放到 SquareMatrix,还是SquareMatrixBase中?
矩阵可以用一个一维数组T data[n*n]来存放。而具体要存放多少数据,最清楚的应该是SquareMatrix,而SquareMatrixBase要想操作矩阵,可以用一个指针指向该数组。
// 使用静态一维数组存放矩阵内容示例
template<typename T>
class SquareMatrixBase {
protected:
SquareMatrixBase(std::size_t n, T* pMem) : size(n), pData(pMem) {}
void setDataPtr(T* ptr) { pData = ptr; }
...
private:
std::size_t size; // 矩阵大小
T* pData; // 指向矩阵内容
};
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> {
public:
SquareMatrix() : SquareMatrixBase<T>(n, data) { } // 用构造函数初始化列表将矩阵大小和数据指针传递给base class
...
private:
T data[n * n]; // 存放矩阵内容
};
也可以通过动态分配内存,把矩阵的数据放进heap,然后将其交给base class。
上面SquareMatrix的做法是通过函数参数,消除template非类型参数(矩阵尺寸)。
有些可能是由于类型参数带来的,如一些平台int和long有相同二进制表述,所以vector
小结
1)template生成多个class和多个函数,所以任何template代码都不该与某个造成膨胀的template参数参数相依关系;
2)因非类型模板参数(non-type template paramter)而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数;
3)因类型参数(type parmaeter)而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述(binary representation)的具现类型(instantiation type)共享实现代码;
[======]
条款45:运用成员函数模板接受所有兼容类型
Use member function template to accept "all compatible types."
真实指针隐式转换
智能指针(smart pointer)是“行为像指针”的对象,并提供指针没有的功能。
如,条款13提到的shared_ptr, unqiue_ptr,如何被利用起来在正确时机自动删除heap-based资源。
真实指针做得很好的一件事,就是支持隐式转换(implicit conversion),体现在2方面:
1)derived class指针可以隐式转换为base class指针;
2)指向non-const对象的指针,可以转换为指向const对象,而无需显式转型(cast);
例如,
class Top { ... };
class Middle: public Top { ... };
class Bottom: public Middle { ... };
Top* pt1 = new Middle; // 将Middle*转换为Top* (Middle* => Top*)
Top* pt2 = new Bottom; // Bottom* => Top*
const Top* pt2 = pt1; // const Top* => Top*
智能指针隐式转换
但如果想要用智能指针模拟上述转换,如何进行?
比如,要施行以下转换:
template<typename T>
class SmartPtr {
public:
explicit SmartPtr(T* realPtr);
...
};
// 客户端想要进行的智能指针转换
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle); // SmartPtr<Middle> => SmartPtr<Top>
SmartPtr<Top> pt2 = SmartPtr<Bottom>(new Bottom); // SmartPtr<Bottom> => SmartPtr<Top>
SmartPtr<const Top> pt3 = pt1; // SmartPtr<Top> => SmartPtr<const Top>
template的2个具现SmartPtr
Template和泛型编程(Generic Programming)
上面例子中,客户端进行智能指针转换时,其实都是创建了一个新的智能指针对象,我们可以关注如何编写智能指针的构造函数,以满足我们的转型需要。
member template(成员函数模板)
那么如何为template class编写构造函数呢?
如果我们为SmartPtr
实际上,需要构造函数的数量是没有止尽的,因为一个template可以被无线具现化,以至于生成无限量函数。因此,我们需要的不是为SmartPtr写构造函数,而是为它写一个构造模板。这样的模板就是所谓的member function template(简称member template),作用是为class生成函数:
// 构造模板, 对任何类型的T, U, 可以根据SmartPtr<U>对象生成一个SmartPtr<T>对象
template<tyepname T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other); // member template,为了生成copy构造函数
...
};
// 客户端调用: SmartPtr<U> => SmartPtr<T>, 其中T和U是任意类型
SmartPtr<U> pu = SmartPtr<U>(new XXX);
SmartPtr<T> pt = pu;
这种 用类型为T的模板类对象构造类型为U的模板类的构造函数,我们称之为“泛化copy构造函数”。
为什么上面的泛化copy构造函数并未声明为explicit?
这是蓄意的,因为原始指针类型之间的转换是隐式转换,无需明白写出转型动作(cast),所以让智能指针效仿这种行径也是合理的。在模板化构造函数(templatized constructor)中略去explicit就是为了这个目的。
如何编写“返回copy构造函数”?
完成声明后,如何编写“返回copy构造函数”? 我们希望根据一个SmartPtr
假设SmartPtr遵循unique_ptr和shared_ptr所提供的榜样,也提供get成员函数,返回智能指针对象(条款15)所持有的原始指针的副本,那么我们可以在“构造模板”实现代码中约束转换行为,使得它符合我们的期望:
template<typename T>
class SmartPtr {
public:
// 构造模板, i.e. 泛化copy构造函数
template<typename U>
SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get()) { ... } // 以other的heldPtr初始化this->heldPtr
T* get() const { return heldPtr; } // 返回原始指针的副本
...
private:
T* heldPtr; // SmartPtr持有的内置指针(原始指针)
};
我们的构造模板是使用成员初值列(member initialization list)来初始化SmartPtrSmartPtr<U>
持有)作为初值。前提条件:存在隐式转换,可以将U指针转换为T指针。也就是说,该构造函数只有在获得与其实参兼容类型时,才通过编译。
member function template(成员函数模板)并不局限于构造函数,也经常应用于赋值操作。如shared_ptr,unique_ptr,weak_ptr的构造行为,以及除weak_ptr外的赋值操作(why?)。
例如,TR1规范中shared_ptr的一份摘录,
template<class T>
class shared_ptr {
public:
template<class Y>
explicit shared_ptr(Y* p); // 构造来自兼容的内置指针
template<class Y>
shared_ptr(shared_ptr<Y> const& r); // 构造来自兼容的shared_ptr. 泛化copy构造函数
template<class Y>
explicit shared_ptr(weak_ptr<Y> const& r); // 构造来自兼容的weak_ptr
template<class Y>
explicit shared_ptr(unique_ptr<Y>& r); // 构造来自兼容的unique_ptr
template<class Y>
shared_ptr& operator=(shread_ptr<Y> const& r); // assignment操作符, 来自兼容的shread_ptr
template<class Y>
shared_ptr& operator=(unique_ptr<Y>& r); // assignment操作符, 来自兼容的unique_ptr
...
};
上面所有的构造函数都是explicit,只有“泛化copy构造函数”除外,为什么?
因为这样意味着某个shared_ptr类型隐式转换为另一个shared_ptr类型是被允许的,但从内置指针或其他智能指针类型进行隐式转换是不被认可的。不过,如果是显示转换比如cast强制转型,则是可以的。
为什么传给构造函数、operator=的 unique_ptr
因为条款13提到,复制unique_ptr,会导致原来的unique_ptr改变(指向null)。这是由unique_ptr的特性决定的:同一时刻,只允许一个unique_ptr指向一个给定对象。当把原来unique_ptr指向的对象,交由新unique_ptr指向时,原来的unique_ptr就指向了null。
声明泛化copy构造函数和copy构造函数
member template并不改变语言规则。条款5提到,编译器可能为我们生成4个成员函数:默认构造函数,copy构造函数和copy assignment操作符,析构函数。(注:C++11新增移动构造函数)
如果程序需要一个copy构造函数,而你却没有声明它,编译器会为你暗自生成一个。在class内声明泛化copy构造函数(是个member template)并不会阻止编译器生成它们自己的copy构造函数(一个non-template),所以如果你想要控制copy构造的方方面面,你必须同时声明泛化copy构造函数和“正常的”copy构造函数。相同的规则也适用于赋值(assignment)运算符。
下面是shared_ptr的一份定义摘要:
tempalte<class T>
class shared_ptr {
public:
shared_ptr(shared_ptr const& r); // copy构造函数
template<class Y>
shared_ptr(shared_ptr<Y> const& r); // 泛化copy构造函数
shared_ptr& opeartor=(shared_ptr const& r); // copy assignment
template<class Y>
shared_ptr& opeartor=(shared_ptr<Y> const& r); // 泛化copy assignment
...
};
小结
1)请使用member function template(成员函数模板)生成“可接受所有兼容类型”的函数;
2)如果你声明member template用于“泛化copy构造”或者“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。
[======]
条款46:需要类型转换时请为模板定义非成员函数
Define non-member functions inside template when type conversions are desired.
本条款类同条款24。条款24讨论过:为什么只有non-member的函数才有能力“在所有实参身上实施隐式式类型转换”。本条款同样以Rational class的operator*为例。
// 条款24的Rational的template版本
template<typename T>
class Rational {
public:
Rational(const T& numerator = 0, const T& denominator = 1); // 参数以passed by reference方式传递避免拷贝, 同时修改可以影响实参
const T numerator() const; // 分子
const T denominator() const; // 分母
...
};
// non-member函数重载operator*
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs)
{ ... }
但与条款24示例不同的是,
Rational
Rational
同样是重载operator*,这里的例子为什么会报错?
条款24内,编译器知道我们尝试调用什么函数(接受2个Rational参数的operator),但这里编译器却不知道我们想要调用哪个函数,因为它们想试图想出什么函数被名为operator的template具现化(产生)出来。而要具现化某个“名为operator并接受2个Rational
为了推动出T,先看operator
1)以oneHalf(Rational
2)以数字2(int)进行推导,由于template实参推导过程从不将隐式类型转换函数纳入考虑(也不考虑通过构造函数而发生的隐式类型转换),编译器并不能使用Rational
有一个简便方法:
template class内friend声明式可以指涉某个特定函数。意味着class Rational
template<typename T>
class Rational {
public:
friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);
// 由于在class template内, template名称(Rational)可以被用来作为“template和其参数”(Rational<T>)的简略表达式
// 因此, 上面声明式等价于下面的声明式 <=>
friend const Rational operator*(const Rational& lhs, const Rational& rhs) { // 使用简略表达式
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
};
注意:如果将operator的实现放到class template外部(如.cpp文件),能通过编译,但无法链接。
能通过编译原因:因为编译器通过模板函数operator,知道我们要调用哪个函数。
无法链接原因:不能在class template外部定义operator* template,必须在class内部定义。
当然,我们可以通过operator* template调用一个定义在外部的函数doMultiply,来简化operator* 。
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
template<typename T>
class Rational {
public:
friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);
// 由于在class template内, template名称(Rational)可以被用来作为“template和其参数”(Rational<T>)的简略表达式
// 因此, 上面声明式等价于下面的声明式 <=>
friend const Rational operator*(const Rational& lhs, const Rational& rhs) { // 使用简略表达式
return doMultiply(lhs, rhs);
}
};
小结
1)当我们编写一个class template,请将那些与template相关的、用于隐式类型转换的函数 定义为“class template内部的friend函数”。
隐含2方面:class template的friend函数,内部实现。
[======]
条款47:请使用traits classes表现类型信息
Use traits clases for information about types.
STL中,容器和算法是分开的,通过迭代器联系到一起。算法如何从迭代器类中萃取出容器元素的类型呢?
这就需要用到traits class技术。
在用traits class技术之前,先看下如何获得元素类型:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d); // 函数模板:将迭代器iter移动d位置
// 问题:如何得到IterT所指元素类型?
// 如果IterT支持 随机访问(+=)操作,advance要做的事就是iter += d;
// 如果不支持,advance要做的事就是 反复施行++iter(或--iter)一共d次。
C++有5种常用迭代器:input(输入),output(输出),forward(前向),bidirectional(双向),random access(随机访问)。
C++ STL分别提供专属卷标结构(tag struct)加以确认:
struct input_iterator_tag
{ // identifying tag for input iterators
};
struct output_iterator_tag
{ // identifying tag for output iterators
};
struct forward_iterator_tag
: input_iterator_tag
{ // identifying tag for forward iterators
};
struct bidirectional_iterator_tag
: forward_iterator_tag
{ // identifying tag for bidirectional iterators
};
struct random_access_iterator_tag
: bidirectional_iterator_tag
{ // identifying tag for random-access iterators
};
5类迭代器详细介绍参见:C++Primer中文版5th Page366,或者 https://blog.csdn.net/CSDN_564174144/article/details/76231626
迭代器如何得到IterT所指元素类型,并实现advance?
一种方案是:
template <typename IterT, typename DistT>
void advance(Iter& iter, DistT d)
{
if (iter is a random access iterator) { // 只有random access iterator支持随机访问操作
iter += d; // random access迭代器使用迭代器算术运算
}
else {
if (d >= 0) { while(d--) ++iter; }
else { while (d++) --iter; }
}
}
这种做法首先必须判断iter是否为random access迭代器,即iter类型(Iter&)是否为random access迭代器。而要获得Iter代表的类型,我们可以通过traits class技术,在编译期就能获得。
traits class技术
traits class技术并不是C++关键字,而是C++程序员共同遵守的协议。其要求之一是:对内置(build-in)类型和用户自定义(user-defined)类型的表现必须一样好。
具体来说,是将类型的traits(特征)信息放入一个template及其特化版本中,不同的容器模板实例化时,类型信息在不同特化版本中携带的traits信息不一样。
利用迭代器萃取元素类型信息示例:
// 通用版本
template<class IterT>
struct my_iterator_traits {
typedef typename IterT::value_type value_type; // traits信息
};
// 偏特化版本
template<class IterT>
struct my_iterator_traits<IterT*> {
typedef IterT value_type; // traits信息
};
void fun(int a) {
cout << "func(int) is called" << endl;
}
void fun(double a) {
cout << "func(double) is called" << endl;
}
void fun(char a) {
cout << "func(char) is called" << endl;
}
// 客户端
int main()
{
my_iterator_traits<vector<int>::iterator>::value_type a = 0;
fun(a); // 打印"func(int) is called"
my_iterator_traits<vector<double>::iterator>::value_type b = 1;
fun(b); // 打印"func(double) is called"
my_iterator_traits<vector<char>::iterator>::value_type c = 2;
fun(c); // 打印"func(char) is called"
return 0;
}
traits class技术简要介绍参见
https://blog.csdn.net/lihao21/article/details/55043881
小结
1)traits class使得“类型相关信息”在编译期可用,使用template和template偏特化实现。
[======]
条款48:认识template元编程
Be aware of template metaprogramming.
元编程具体内容,暂略。
小结
1)Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率;
2)TMP可被用来生成“基于政策选择组合”(based on combinations of policy choices)客户定制代码,也可用来避免生成对某些特殊类型并不合适的代码。
[======]