模版与泛型编程
c++template机制自身是一部完整的图灵机:它可以被用来计算任何可计算的值。
了解隐式接口和编译期多态
面向对象编程世界总是以显示接口和运行期多态解决问题。
template及泛型编程的世界,与面向对象有根本上的不同,在此世界中显示接口和运行期多态仍然存在,但重要性降低,反倒是隐式接口和编译器多态移到前头啦。
通常显示接口由函数的签名式构成。
class Widget{
public:
Widget();
virtual ~Widget();
virtual std::size_t size() const;
virtual void normalize();
void swap(Widget& other);
};
它的public接口由一个构造函数,一个析构函数,函数及其参数类型,返回类型,常量性构成,也包括编译器产生的copy构造函数和copy assignment操作符。
隐式接口就完全不同了,他并不是基于函数签名式,而是由有效表达式组成:
template<typename T>
void doProcessing(T& w){
if(w.size() > 10 && w != someNastyWidget){
//...
}
}
- 它必须提供一个名为size的成员函数,该函数返回一个整数值
- 它必须支持一个operator!= 函数,用来比较两个T对象。
- ….
多态这是通过template具现化和函数重载解析发生于编译期来实现的。
了解typename的双重意义
template<class T> class Widget; //使用“class”
template<typename T> class Widget; //使用“typename”
上述两种写法完全一样,没有不同的意义。然而c++并不总是把class和typename视为等价。有时候一定得使用typename。
template<typename C>
void print2nd(const C& container){
if(container.size() >= 2){
C::const_iterator iter(container.begin()); //false的用法
++iter;
int value = *iter;
std::cout<<value;
}
}
template内出现的名称如果相依于某个template参数,称为从属名称,如果从属名称在class内呈嵌套状,称为嵌套从属名称,例如:C::const_iterator
就是一个嵌套从属类型名称
不依赖于任何template参数的名称,称为非从属名称(non-dependent names)。
如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在临近它的前一个位置放上关键字typename。
template<typename C>
void print2nd(const C& container){
if(container.size() >= 2){
typename C::const_iterator iter(container.begin()); //false的用法
//...
}
}
typename只被用来验明嵌套从属类型名称,其他名称不该有它存在
template<typename c>
void f(const C& container, typename C::iterator iter);
“typename必须作为嵌套从属类型名称的前缀词”这一规则有一个例外是,typename不可以出现在base class list内的嵌套从属类型名称前,也不可以出现这membr initialization list中作为base class修饰符。
template<typename T>
class Derived: public Base<T>::Nested{
public:
explicit Derived(int x): Base<T>::Nested(x){
typename Base<T>::Nested temp;
}
};
可以使用typedef来简化名称长度
template<typename IterT>
void workWithIterator(IterT iter){
//typename std::iterator_traits<IterT>::value_type value_type temp(*iter);
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
}
学习处理模板化基类内的名称
class CompanyA{
public:
void sendCleartext(const std::string& msg);
void sendEncrypted(cosnt std::string& msg);
//...
};
class CompanyB{
public:
void sendCleartext(const std::string& msg);
void sendEncrypted(cosnt 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);
}
void sendSecret(const MsgInfo& info){ //同上
//...
}
};
//假设需要在每次送出信息时标记某些信息
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info){
//将传送前的信息写到log
sendClear(info); //调用base class函数,但是无法通过编译
//将传送后的信息写到log
}
}
上述的类,在编译器是无法通过编译,因为class template定义式时,并不知道它继承什么样的class。因为Company是template参数,不到被具现化,无法确切知道它是什么。因此,就没法知道class MsgSender<Company>
看起来像什么–更明确的说是不知道它是否有个sendClear函数。
//全特化类,因为companyz不需要sendClear函数
template<>
class MsgSender<CompanyZ>{
public:
void sendSecret(const MsgInfo& info);
}
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info){
//将传送前的信息写到log
sendClear(info); //如果Company = CompanyZ,这个函数不存在
//将传送后的信息写到log
}
}
//因为companyz没有提供sendclear函数
为了解决上述问题(令c++不进入templatized base class观测的行为失效),有三种解决方法:
1.在base class函数调用动作之前加上this->
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info){
//将传送前的信息写到log
this->sendClear(info); //假设sendClear将被继承
//将传送后的信息写到log
}
}
2.使用using声明式:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
using MsgSender<Company>::sendClear;
void sendClearMsg(const MsgInfo& info){
//将传送前的信息写到log
sendClear(info); //假设sendClear将被继承下来
//将传送后的信息写到log
}
}
虽然using在这里和条款33都可有效运作,但是解决的问题不同。33里面using为了解决derived class名称遮蔽问题。这里是编译器不进入base class作用域内查找,于是通过using告诉它,请它这么做
3. 明白指出被调用的函数位于base class内:
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
void sendClearMsg(const MsgInfo& info){
//将传送前的信息写到log
MsgSender<Company>::sendClear(info); //假设sendClear被继承下来
//将传送后的信息写到log
}
}
缺点:如果被调用的是virtual函数,上述明确的资格修饰将会关闭virtual绑定行为。
以上的三种解法,都是对编译器承诺base class template 的任何特化版本都将支持其一般化版本所提供的接口。
将与参数无法的代码抽离template
使用template可能会导致代码膨胀:其二进制码带着重复的代码、数据。其结果有可能源码看起来合身而整齐,但目标码却不是那回事。
通过共性与变性分析,使其概念十分平民化。
template<typename T, std::size_t n>
class SquareMatrix{
public:
void invert(); //矩阵求逆
};
SquareMatrix<double, 5> sm1;
sm1.invert();
SquareMatrix<double, 10> sm2;
sm2.invert();
//导致template代码膨胀的一个例子
为了解决代码膨胀的问题:
tempalte<typename T>
class SquareMatrixBase{
protected:
void invert(std::size_t matrixSize);
};
template<typename T, std::size_t n>
class SquareMatrx: private SquareMatrixBase{
private:
using SquareMatrixBase<T>::invert; //避免遮蔽base版本的invert
public:
void invert(){ this->invert(n);}
};
但是上述的实现方法,SquareMatrixBase::invert不知道操作什么数据,虽然它从参数中知道矩阵的尺寸,但它如何知道哪个特定矩阵的数据在哪儿?只有derived class知道,因此要联络他们二者一起来实现逆运算动作。
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 SquareMatirx: private SquareMatrixBase<T>{
public:
SquareMatrix(): SquareMatrixBase<T>(n,data){}
private:
T data[n*n];
};
//动态分配内存的方法
template<typename T, std::size_t n>
class SquareMatirx: private SquareMatrixBase<T>{
public:
SquareMatrix(): SquareMatrixBase<T>(n,0), pData(new T[n*n]) { this->setDataPtr(pData.get()); }
private:
boost::scoped_array<T> pData;
};
以上讨论的是非类型模版参数导致的代码膨胀,其实typename parameters也会导致膨胀。例如,在某些平台上vector和vector成员函数有可能完全相同。如果你实现某些成员函数而它们操作强类型指针,你应该令它们调用另一个操作无类型指针的函数,由后者完成实际工作。
运用成员函数模版接受所有兼容类型
真实指针做的很好的一件事是,支持隐式转换。但是template的具现体之间不存在什么与生俱来的固有关系,意思是:以带有base-derived关系的B,D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系。因此为了达到隐式转换的目的,必须明确的编写出来。
template和泛型编程
为了实现一个智能指针,我们可以通过member function template 的方法来实现:
template<typename T>
class SmartPtr{
public:
template<typename U> //泛化copy构造函数
SmartPtr(const SmartPtr<U>& other);
//....
};
//对于任何类型T和任何类型U,可以根据SmartPtr<U>生产一个SmartPtr<T>。
但是上述的实现,可能会导致SmartPtr转换为SmartPtr情况的出现。这样就导致了错误,因为必须对member template所创建的成员函数进行筛除。
template<typename T>
class SmartPtr{
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other): heldPtr(other.get()){ //....}
T* get() const { return heldPtr;}
private:
T* heldPtr;
};
//只有当存在某个隐式转换可将一个U*指针转为一个T*指针时,才通过编译。
member functiontemplate的效用除了上面的构造函数,还扮演着另一个角色是支持赋值操作。并且他不改变语言基本规则。如果程序需要一个copy构造函数,没有声明它,编译器会给你声明一个。如果想要控制copy构造的方方面面,必须同时声明泛化copy构造函数和正常的拷贝构造函数。
template<class T>
class shared_ptr{
public:
shared_ptr(shared_ptr const& r);
template<class Y>
shared_ptr<shared_ptr<Y> const& r);
shared_ptr& operator=(shared_ptr const& r);
tempalte<class Y>
shared_ptr& operator=(shared_ptr<Y> const& r);
};
需要类型转换时请为模版定义非成员函数
template<typename T>
class Rational{
public:
Rational(const T& numerator = 0, const T& denominator = 1);
const T numerator() const;
const T denominator() const;
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{
//...
}
Rational<int> onehalf(1,2); //true;
Rational<int> result = onehalf * 2; //false 无法通过编译
模板化的Rational和non-template版本不同。因为template的实参推导过程中不将隐式类型转换函数纳入考虑,因为上述调用中,无法推导出2的类型。因此编译不通过。为了解决template实参推导的问题,可以将函数声明为friend声明式。
template<typename T>
class Rational{
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
template<typename T>
const Ration<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
//..
}
//上述函数可以编译成功,因为firend函数可以使用隐式转换函数,使得混合式调用成功。
//但是无法通过链接,找不到定义,因为他只被声明于Rational内。没有定义出来
//解决无法链接的问题
template<typename T>
class Rational{
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs){
return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator());
}
};
另一种方法,利用friend函数调用辅助函数。
template<typename T> class Rational;
template<typename T>
const Rational<T> doMultiple(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:
friendconst Rational operator*(const Rational& lhs, const Rational& rhs){
return doMultiple(lhs,rhs);
}
};
请使用traits class表现类型信息
traits是一种技术,他对内置类型和用户自定义类型的表现必须一样好。
1.确认若干你希望将来可取的的类型相关信息。例如对迭代器而言,希望将来可取的其分类
2.为该信息选择一个名称
3.提供一个template和一组特化版本,内含你希望支持的类型相关信息。
4.建立一组重载函数或函数模版,彼此之间的差异只在于各自的traits参数。
5.建立一个控制函数或函数模版,他调用上述那些函数并传递traits class所提供的信息。
struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidirectional_iterator_tag: public forward_iterator_tag{};
struct random_access_iterator_tag: public bidirectional_iterator_tag{};
//advance实现方式,使用traits技术
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d){
if(iter is a random access iterator){
iter += d;
}
else{
if(d >= 0) {while(d--) ++iter;}
else{ while(d++) --iter;}
}
}
template<typename IterT, typename DistT>
void doAdvance(IterI& iter, DistT d, std::random_access_iterator_tag){
iter += d;
}
template<typename IterT, typename DistT>
void doAdvance(IterI& iter, DistT d, std::bidirectional_iterator_tag){
if(d >= 0) {while(d--) ++iter;}
else{ while(d++) --iter;}
}
template<typename IterT, typename DistT>
void doAdvance(IterI& iter, DistT d, std::input_iterator_tag){
if(d < 0) { throw std::out_of_range("Negative distance");}
while(d--) ++iter;
}
template<typename IterT, typename DistT>
void advance(IterI& iter, DistT d){
doAdvance(iter,d,typename std::iterator_traits<IterT>::iterator_category());
}
//traits class的定义
template<typename IterT>
struct iterator_traits<IterT*>{
typedef random_access_iterator_tag iterator_category;
/...
}
认识template元编程
所谓template元编程是以c++写成,执行于C++编译器内的程序。优点:1,它让某些事情更容易;2,由于template 元编程执行与C++编译器,因此可将工作从运行期移到编译期。
template元编程没有真正的循环构建,主要是由递归完成的。采用的不是递归函数调用,而是递归模版具现化。
template<unsigned n>
struct Factorial{
enum{value = n * Factorial<n-1>::value};
};
template<>
struct Factorial<0>{
enum{value = 1};
};
template元编程值得学习的例子:1. 确保度量单位正确;2.优化矩阵计算(可以消除临时对象并合并循环);3.可以生产客户定制的设计模型实现品。