classes和templates都支持接口和多态。
对classes而言接口是显示的,以函数签名为中心。多态则是通过virtual函数发生于运行期。
对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。
隐式接口:函数模板中出现的接口,此template必须对其编译有效。
template<typename T>
void doProcessing(T& w){
if (w.size() > 10 ) {//size()和operator>是隐式接口
T temp(w); //copy构造函数是隐式接口
temp.normalize();//normalize()是隐式接口
}
}
编译期多态:以不同的template参数具现化function templates会导致调用不同的函数。
声明template参数时,前缀关键字class和typename可互换。
请使用关键字typename标识嵌套从属类型的名称;但不得在base class lists(基类列)或member initialization list内以他作为base class修饰符。
从属名称:template内出现的名称如果相依于某个template参数,称之为从属名称。如果从属名称在class内成嵌套状,称为嵌套从属名称。
template<typename T>
void doProcessing(T& w){
if (w.size() > 10 ) {
T temp(w);
//const_iterator是一个嵌套从属名称
typename T::const_iterator iter(w.begin());
temp.normalize();
}
}
typename有一个用途是被用来验证嵌套从属类型名称。
template<typename T>
void func(){
//下面这条语句有两种意义
//const_iterator是一个嵌套从属名称x是其类型的指针
//T中名为const_iterator的成员变量与x相乘
T::const_iterator* x;
}
//修改为
template<typename T>
void func(){
//使用typename声明其位嵌套从属名称。
typename T::const_iterator* x;
}
typename必须作为嵌套从属类型名称的前缀词这一规则的例外:
base class list中不允许typename。
member initialization list(成员初值列表)内不允许typename。
可在derived class templates内通过this->指涉base class templates内的成员名称,或籍由一个明白书写出”base 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);//密文
};
//将MsgSender具现化为不同的公司以传递信息。
template<typename Company>
class MsgSender{
public:
void sendClear(const std::string& info){
std::string msg;
//根据info设置msg;
Company c;
c.sendCleartext(msg);
}
void sendSecret(const std::string& info){
//类似的实现
}
};
现在我们希望每次送出信息时志记(log)某些信息。我们写一个dreived class来实现。
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
void sendClearMsg(const std::string& info){
//传送前的信息写至"log"
sendClear(info);//ERROR
//传送后的信息写至"log"
}
};
sendClear(info);语句将会无法通过编译,因为编译器遭遇class template LoggingMsgSender定义式时,并不知道他继承什么样的class,因为MsgSender< Company>的 Company不知道是什么。,也无法知道class MsgSender< Company>是否有个sendClear函数。例如:
若有一个公司Z只使用密文:
class CompanyZ{
public:
void sendEncrypted(const std::string& msg);//密文
};
//对CompanyZ产生一个MsgSender特化版本
//template<>语法象征这既不是template也不是标准class
//而是特化版的MsgSender template
//在template实参是CompanyZ时使用。
template<>
class MsgSender<CompanyZ>{
public:
void sendSecret(const std::string& info);
};
再看class LoggingMsgSender的sendClear(info);语句,如果Company = CompanyZ,那么这条语句将不存在。
C++拒绝sendClear(info);调用的原因是它知道base class templates有可能被特化,而那特化版本可能不提供和一般性template相同的接口,但是我们有三种方法告诉编译器sendClear(info);是可以调用的。
template<typename Company>
class LoggingMsgSender:public MsgSender<Company>{
//1.使用using声明告诉编译器,sendClear在base class内
using MsgSender<Company>::sendClear;
void sendClearMsg(const std::string& info){
//2.使用this,指明sendClear被继承
this->sendClear(info);
//3.指明sendClear在base class内
//但sendClear是virtual函数,会关闭virtual绑定行为
MsgSender<Company>::sendClear(info);
}
};
Templates生成多个classes和多个函数,所以任何template代码都不该与某个人造成膨胀的template参数产生相依关系。
因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参会或class成员变量替换template参数。
因类型参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同二进制表述的具现类型共享实现码。
若我们为固定尺寸的正方矩阵编写一个template,实现求逆矩阵运算。
template<typename T,std::size_t n>
class SquareMatrix{ public: void invert(); };
上述这样的代码,在n不同时将会产生不同的class造成代码膨胀。我们应该将共性分离出来。
template<typename T>
class SquareMatrixBase{
public:
SquareMatrixBase(std::size_t n,T* pMem);//n为矩阵大小pMem为矩阵指针
void invert();
......
};
template<typename T,std::size_t n>
class SquareMatrix:private SquareMatrixBase<T>{
public:
SquareMatrix():
SquareMatrixBase<T>(n,data){}
void invert(){
SquareMatrixBase<T>::invert();
}
private:
T data[n*n];
};
请使用member function templates(成员函数模板)生成”可接受所有兼容类型”的函数。
如果你声明member templates用于”泛化copy构造”或”泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。
假如我们想实现智能指针,考虑到其copy构造函数若为普通构造函数写法,那么将具现化无限多的函数,因为其继承关系需要不同的copy构造函数来体现。解决方法是使用构造模板。这样的模板是所谓member function templates,简称member templates,其作用是为class生成函数。
在class内声明泛化的构造函数并不会阻止编译器生成他们自己的copy构造函数,若想控制“正常的”(非泛化)的copy构造函数,那么需要在声明一个。
template<typename T>
class SmartPtr{
public:
//member templates,泛化copy构造函数
template<typename U>
SmartPtr(const SmartPtr<U>& other)
:heldPtr( other.get() )//利用原始指针实现的继承性来确保智能指针的继承性
{}
//non-template,正常的copy构造函数
SmartPtr(const SmartPtr<T>& other):
heldPtr( other.get() )
{}
T* get() const{ return heldPtr; }
private:
T* heldPtr;//原始指针
};
当我们编写一个class template,而它所提供之”与此template相关的”函数支持”所有参数之隐式类型转换”时,请将那些函数定义为”class template内部的friend 函数”
将24的有理数相乘功能使用template实现
template<typename T>
class Rational{
public:
//non-explicit的构造函数
Rational(const T& numerator = 0,const T& denominator = 1):
_numerator(numerator),
_denominator(denominator)
{}
//getter函数
const T numerator() const{
return _numerator;
}
const T denominator() const{
return _denominator;
}
private:
T _numerator;//分子
T _denominator;//分母
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs){
return Rational<T>(lhs.numerator()*rhs.numerator(),
lhs.denominator()*rhs.denominator()
);
}
int main(int argc, const char * argv[]) {
Rational<int> onHalf(1,2);
//ERROR: Invalid operands to binary expression ('Rational<int>' and 'int')
Rational<int> result = onHalf * 2;
return 0;
}
然而发现Rational<int> result = onHalf * 2;
语句出现错误。因为template实参推导过程中从不将隐式类型转换函数纳入考虑。所有编译器寻找这样的函数
const Rational<int> operator*(const Rational<int>& lhs,const int& rhs);
然而是不存在的,所有会报错。
解决方法是将operator声明为friend函数。因为当对象oneHalf被声明为一个Rational<int>
,class Rational<int>
被具现化出来,而作为过程的一部分,friend函数operator*也就被自动声明出来。后者身为一个函数而非函数模板,因此编译器可以在调用他 时使用隐式转换函数。
但是仅仅这样缺无法通过链接,因为我们意图令此class外部的operator* template提供定义式,但是行不通——如果我们自己声明了一个函数,就有责任定义那个函数,既然我们没有提供定义式,那么链接器就找不到它。所以需要在class内提供其定义式。
由于在class声明内提供定义式,会被inline,所以较复杂的函数应该调用一个定义于class之外的辅助函数。
template<typename T>
class Rational;
//辅助函数
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs){
return Rational<T>(lhs.numerator()*rhs.numerator(),
lhs.denominator()*rhs.denominator()
);
}
template<typename T>
class Rational{
public:
//non-explicit的构造函数
Rational(const T& numerator = 0,const T& denominator = 1):
_numerator(numerator),
_denominator(denominator)
{}
//声明为friend,并在类中定义
//在class template内,template名称可用来作为template和其参数的简略表示
//所以可以写Rational,不必写Rational<T>,不过写后者也ok
friend
const Rational operator*(const Rational& lhs,const Rational& rhs){
return doMultiply(lhs, rhs);//调用辅助函数
}
//getter函数
const T numerator() const{
return _numerator;
}
const T denominator() const{
return _denominator;
}
private:
T _numerator;//分子
T _denominator;//分母
};
int main(int argc, const char * argv[]) {
Rational<int> onHalf(1,2);
Rational<int> result = onHalf * 2;
return 0;
}
Traits classes使得”类型相关信息”在编译期可用。他们以templates和”templates 特化”完成实现。
整合重载技术后,traits classes有可能在编译期对类型执行if…else测试。
traits classes是一种用来表现类型信息的技术。
例如STL中的advance函数,他的作用是给定迭代器和偏移位置,返回经过偏移后的迭代器。所以我们需要知道输入的迭代器类型信息,因为如果迭代器是随机访问迭代器,那么会有速度更快的算法用来选择。
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d){
if (iter is radom access iterator ) {//需要迭代器类型来选择算法
iter += d;
}else{
if (d > 0) {
while (d--) {
++iter;
}else{
while (d++) {
--iter;
}
}
}
}
}
下面以iterator_category作为例子解释traits技术
首先使用typedef关键字将类拥有的迭代器命名为iterator_category。
如deque使用随机访问迭代器random_access_iterator_tag,list使用bidirectional_iterator_tag
template<typename T>
class deque{
public:
class iterator{
public:
typedef std::random_access_iterator_tag iterator_category;
};
};
template<typename T>
class list{
public:
class iterator{
public:
typedef std::bidirectional_iterator_tag iterator_category;
};
};
iterator_traits则是相应iterator class的嵌套式typedef
template<typename IterT>
struct iterator_traits{
typedef typename IterT::iterator_category iterator_category;
};
//针对指针迭代器的偏特化版本。指针与random_access_iterator_tag类似
template<typename IterT>
struct iterator_traits<IterT*>{
typedef random_access_iterator_tag iterator_category;
};
现在可以实现advance的if内的语句iter is radom access iterator了
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d){
if (typeid(typename std::iterator_traits<IterT>::iterator_category)
== typeid(std::random_access_iterator_tag)
) {
}
}
我们可以把if判定移到编译期来作,方法是使用函数的重载功能
template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d){
doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}
template<typename IterT,typename DistT>
void doAdvance(IterT& iter,DistT d,std::random_access_iterator_tag);
template<typename IterT,typename DistT>
void doAdvance(IterT& iter,DistT d,std::bidirectional_iterator_tag);
//其他迭代器类型的重载函数
TMP可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。
TMP可被用来生成”基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。
template meta programming(TMP,模板元编程)是编写template-base c++程序并执行于编译期的过程。