c++ primer 模板与泛型编程学习小结

c++ primer 专栏收录该内容
1 篇文章 0 订阅

模板与泛型编程(Templates and generic programming) p229
什么是模板和泛型编程?
泛型编程就是编写和类型无关的逻辑代码,是代码复用的另一种手段,以独立于任何特定类型的方式编写代码,代码与所处理的类型独立。
模板是实现泛型编程的一种技术,最初是为了建立类型安全的容器。c++模板机制本身是图灵机,导出模板元编程,实现编译时的编译期多态。

为什么容器需要模板?(也注意下模板与面向对象的多态的区别)
如何实现容器vector的push_back?
1.void push_back(const int iNum) //一个类重载不同参数类型的函数。问题:新增类型很难扩展,可以传入不同类型很难保证类型安全。
2.class vector_int //继承或组合实现支持不同类型。问题:新增类型很难扩展。
3.template<class _Ty,class _Ax>class vector: public _Vector_val<_Ty, _Ax>//定义类模板
void push_back(const _Ty& _Val)//参数类型泛化,实现对不同类型支持,类型可以是int,short,class,cactor,everyone。

1.了解隐式接口和编译期多态 p229
先来看一下与之对应且面向对象常见的显式接口和运行期多态。
场景1:点击导航屏幕上的按钮(事件由CStage分发至子CActor)
virtual EVENT_RESULT CActor :: DispatchMouseEvent(const CEventArg& arg) = 0;//基类定义虚函数
CActor* pstChild = *rIter;//基类指针指向子类对象
pstChild->DispatchMouseEvent(stMouseEvent);//运行期动态的调用不同子类重写函数

a.pstChild是声明为CActor类型。pstChild需要支持CActor的接口DispatchMouseEvent,源码中可以找到这个接口(显式接口)。
显式接口由函数的签名式(函数名,参数类型,返回类型)构成。
b.导航运行时,点击到不同CActor时决定调用哪个CActor子类的函数,即运行期根据动态类型决定函数调用(运行期多态,又称动态多态),通过继承与重写实现。

模板以及泛型编程与面向对象不同的是虽然也有显式接口和运行期多态,但是更重要部分是隐式接口(implicit interfaces)和编译期多态(compile-time polymorphism)。
场景2:框架的引用计数(基于模板的CRefCnt)
template
class CRefCnt
{
CRefCnt& operator=(const CRefCnt& stOther)
{
if (this != &stOther && m_pstObject->AddRef()>0) //框架代码不是这样 都只是为了举例
}
T* m_pstObject;
}

a.pstObject支持的接口由其在模板执行的操作决定,如AddRef和不等比较,这一组表达式(对此模板必须有效编译)是T必须支持的隐式接口。
隐式接口与上边的显示接口有明显的差异,它并不基于函数签名式,而是由有效表达式(valid expressions)组成。
表面上T的隐式接口约束有1.提供返回整数的AddRef成员函数,2.提供支持operator!=函数。
实际上操作符重载和继承带来了更多的可能性,只需要保证this != &stOther && m_pstObject->AddRef()>0的返回与bool兼容(这是if语句带来的约束,注意此处Effective原文主要想表达模板隐式接口的支持比表面上看有更小的约束,很不好理解,需要好好分析。从书201页开始)。
并且与显示接口一样,隐式接口也在编译期完成检查,不支持模板要求的隐式接口的对象,代码同样编译不过。

b.对stOther的任何函数调用都有可能造成模板具现化(instantiated即实例化),这个具现化行为发生在编译器编译时(想想使用引用计数时),“通过不同的模板参数具现化的函数模板”会导致调用不同的函数,这就是编译期多态。
“运行期多态”和“编译期多态”之间的差异 类似于“哪一个重载函数该被调用(发生在编译期)”和“哪一个虚函数该被绑定(发生在运行期)”之间的差异。
(模板和重载是实现编译期的多态的两种方法,也称静态多态)。

请记住的原文摘录:
1)classes和templates都支持接口(interfaces)和多态(polymorphism);
2)对于classes而言接口是显性的(explicit),以函数签名为中心,多态则是通过虚函数发生于运行期。
3)对于template参数而言,接口是隐形的(implicit)以有效表达式为中心,多态则是通过template具现化和函数重载解析(function overloading resolution)发生于编译期。

2.了解typename的双重意义 p233

意义1.class关键字的同义词:
template
template
以上两种模板声明参数方式没有区别,可互相替换。

既然有typename存在,必然有class不可替换的情况。
意义2.类型名指示符:
template
void CArrayAdapter::AddItem(const T& Item)
{
T::Array stArray; //template内出现的名称如果相依于某个template参数,称之为从属名称。stArray类型是T::Array,实际取决于template参数T,所以是从属名称(dependent name)。
//并且::表示在前者作用域内的某实体,通俗的讲就是嵌套的,所以它还是嵌套从属名称(nested dependent name)。
//嵌套从属名称有可能导致解析困难,接下来我们会看到。

int iCount = 0;   
//iCount的类型是int,不依于任何template参数,是独立的名称,即非从属名称(non-dependent name)。

T::Array * stArray;
//问题来了Array可能是一个类型,stArray是一个指针。Array可能是一个变量,这里是Array与stArray相乘,并且此时解析器默认以Array是变量进行解析。

//如果我们希望以Array做类型,就需要typename作为类型名指示符,如下:
typename T::Array * stArray;

}

typename类型名指示符规则:
a.typename只被用来验证嵌套从属类型名称(不考虑意义1时),其他名称不能有它作为前导。
b.上条的例外是typename也不能出现在base class list(基类列表)和members init list(成员初始列表)中。
规则举例:
template //意义1,可以使用typename
void CArrayAdapter::AddItem(const T& Item) //不是嵌套从属类型名称,不可以使用typename
{
typename T::iterator iter; //是嵌套从属类型名称,并且不在base class list和members init list中,一定使用typename
}
class CArrayAdapter:public CUIAdapter::iterator //base class list中不可以使用typename
{
CArrayAdapter()
:CUIAdapter::iterator(i); //members init list中不可以使用typename
}
注意:不同编译器,typename规则有所不同。

最后一个例子是typename的typedef应用,一些较为长的嵌套从属类型名称同样可以使用typename定义别名。
例如:
typename CWeakRef::CWeakRefHandle;
这是嵌套从属类型名称需要有typename,并且我们可以加入typedef定义别名,这样之后直接使用别名就很方便,如下。
typedef typename CWeakRef::CWeakRefHandle HandleType;
CRefCnt m_pstObjHandle;

请记住的原文摘录:
1)声明template参数时,前缀关键字class和typename可以互换(意义1)。
2)请使用关键字typename嵌套从属类型名称;在base class lists或member initialization list内以它作为base class修饰符除外。

3.学习处理模板化基类内的名称 p237

先来了解几个名词:
函数特化(Function Specialization):使用模板时会遇到一些特殊的类型需要特殊处理,不能直接使用当前的模板函数,所以此时我们就需要对该类型特化出一个模板函数(就是写出一个模板函数专门给该类型使用)。
模板特化(Template Specialization):任何针对模板参数进一步进行条件限制设计的特化版本。
模板全特化(Total Template Specialization):即针对所有的模板参数进行特化。例如:template<> class C<int,char>{};(模板中的类型全部确定了)。
模板偏特化(Partial Template Specialization):即针对部分模板参数进行特化。例如:template class C<U,int>{};
模板具现化(Template Instantiation):指函数模板(类模板)生成模板函数(模板类)的过程。

从面向对象的C++转向模板C++时继承可能遭遇的问题:由于基类模板可能被特化,而该特化版本肯可能会改变成员,因此编译器拒绝在模板基类中寻找继承而来的名称。

下面看一下例子,理解一下这个问题:
class CompanyA {
void sendCleartext(const std::string& msg)
};

template
class MsgSender{ //base class基类
void sendClear(){
Company c;
c.sendCleartext();
}
};
template
class LoggingMsgSender:public MsgSender{ //derived class衍生类
void sendClearMsg(){
sendClear();//调用base class函数,这段代码无法通过编译
}
};
编译器无法找到sendClear,原因在于当编译器遇到LoggingMsgSender定义式时,并不知道它继承什么样的class。
它继承的是MsgSender,但其中的Company是个template参数,不到LoggingMsgSender被具现化,无法确切知道它是什么。
(因为sendClear中的Company还不能确定类型),进而无法知道class MsgSender它是否有个senderClear函数。

用实例证明上面的概念:
class CompanyZ{
};

我们为CompanyZ提供MsgSender特化版。
template<>
class MsgSender{
//base class删除了sendClear函数
};
注意在类定义开始的地方出现的”template<>” 语法。它表明这既不是模板也不是单独的类。它是当使用CompanyZ作为模板参数时,会使用到的MsgSender模板的特化版本。这叫做模板全特化(total template specialization):模板MsgSender为类型CompanyZ进行了特化,并且特化是全特化——一旦类型参数被定义为ComanyZ,模板参数的其它地方就不会再发生变化。

template
class LoggingMsgSender:public MsgSender{
void sendClearMsg(){
sendClear();//当Company==CompanyZ时候,这个函数并不存在
}
};
当base class被指定为MsgSender时候这段代码不合适,因为那个class并未提供sendClear函数!那就是为什么C++拒绝这个调用的原因。
他知道base class templates可能会被特化,而那个版本可能不提供和一般性template相同的接口,因此他往往拒绝在templatized base classes内寻找继承而来的名称。
这就是上面提到过的Object Oriented C++到Template C++时候继承可能遭遇的问题。
根本原因其实就是:模板基类对于模板衍生类来说是不确定的。

为了解决这个问题(不进入templatized base classes观察的行为失效)。有三个办法:
a.在调用base class函数之前加上“this->”:
template
class LoggingMsgSender:public MsgSender{
void sendClearMsg(){
this->sendClear();
}
};

b.使用using声明式:
template
class LoggingMsgSender:public MsgSender{
public:
using MsgSender::sendClear;
void sendClearMsg(){
sendClear();
}
};

c.明确指出被调用的函数处于base class内(这种做法不好,因为如果被调用的是virtual函数,明确资格修饰会关闭“virtual绑定行为”。):
template
class LoggingMsgSender:public MsgSender{
public:
void sendClearMsg(){
MsgSender::senderClear();
}
};

上面的方法都是对编译器承诺基类模板的任何特化版本都将支持其泛化版本所提供的接口。这样是编译器解析衍生类时需要的。
承诺只是一方面,如果之后承诺未被遵守,仍然编译不过,也就是说这只是让编译器可以调用模板基类函数,但是如果如上面的例子的调用仍然不通过:
LoggingMsgSender zMsgSender;
zMsgSender.sendClearMsg();
这是编译不过的,因为特化版的基类并未提供sendClear函数。

本条款主要是由于存在模板的特化,模板基类可能存在多个版本,进而模板衍生类无法确定模板基类的内容。

请记住的原文摘录:
1)可在衍生类内通过this->指涉基类模板内的成员名称或由基类资格修饰符完成。

4.将与参数无关的代码抽离templates p242

先来了解几个名词:
类型参数(type parameters):比如我们常见的类型参数,。
非类型模板参数(non-type template parameters):是常整数(包括enum枚举类型)或者指向外部链接对象的指针为参数,,以及下文的<std::size_t n>。

问题1.模板可能导致代码膨胀,需要共性和可变性分析
模板能够节省时间和避免代码重用。5个相同类名,每个类5个成员,你只需要一个类模板,实际用到的时候(只有在被使用的情况下类模版的成员函数才会被隐式的实例化)让编译器来为你实例化5个特定的类和25个函数。函数模板也是如此。但是同样的,使用模板会导致代码膨胀(code bloat):产生重复代码或者数据的二进制文件。结果是源码整齐,但是目标代码(object code即编译器或汇编器处理源代码后所生成的代码)臃肿,本节就是进阶介绍如何避免这种问题。

使用的方法是:共性和可变性分析(commonality and variability analysis)
即如何在问题领域中找到不同变化,如何找到不同领域中的共同点。找到变化的地点,称为“共性分析”,找出如何变化,称为“变性分析”。

不要被这个名字和理论吓到,其实这是我们平时编码常用的分析方式。

例如一个函数实现的某些部分同另外一个函数实现基本上是相同的 ,于是你将两个函数的公共代码提取出来,放进第三个函数中,然后在两个函数中调用这个新函数。总结一下就是,你对两个函数进行分析,找到相同和不同的部分,将相同的部分移到一个新的函数中去,将不同的部分保留在原来的函数中。还比如你正在实现一个类,你意识到类中的一部分另一个类中的一部分是相同的,你不应该重写相同的部分。你可以将相同的部分移到一个新类中,然后使用继承或者组合让原始类访问共同的特性。原始类中不同的部分仍然保留在原来的位置。

当实现模板的时候,也需要相同的分析,和相同的方式来阻止重复。但是在非模板(non-template)代码中,重复是显示的,你可以看到在函数之间或者类之间会有代码重复。
而在模板代码中,重复是隐式的,因为只有一份模板源代码,所以下面开始学习找到和修改重复。

问题2.如何处理非类型模板参数导致的代码膨胀
a.首先是去掉非类型模板参数:
举例为矩阵实现一个模板如下,它提供矩阵转置函数(只需知道这是个计算函数)
template<typename T, // 类型模板参数
std::size_t n> // 非类型模板参数
class SquareMatrix {
void invert(); // 矩阵计算函数
};

下面继续,
SquareMatrix<double, 5> sm1;
sm1.invert();
SquareMatrix<double, 10> sm2;
sm2.invert();
在这里将会实例化invert的两份拷贝。这两个函数并不相同,但是如果不考虑常量5和10,这两个函数将会是一样的。这是使得包含模板的代码出现膨胀的典型方式。
如果是两个普通函数,它们的所有字符都是相同,除了一个使用5而另外一个使用10。你的直觉是会创建一个带一个参数的函数版本,
然后以5或者10为入参调用这个函数而不是重复代码,下面我们以这个思路改进它。

template //抽出一个基类实现invert,声明时候移除非类型模板参数std::size_t
class SquareMatrixBase {
protected:
void invert(std::size_t matrixSize);
};
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase {
private:
using SquareMatrixBase::invert;
public:
void invert() {this->invert(n);} //内联调用base
};

带参数的invert被放在基类SquareMatrixBase中。SquareMatrixBase也是一个模板,但是与SquareMatrix不同的是,它在矩阵中只对类型参数模板化。
因此给定类型对象的所有矩阵将会分享一个单一的SquareMatrixBase类。同样的会共享这唯一的SquareMatrixBase类的invert。(注意你不能将SquareMatrixBase类的invert声明为inline,因为一旦被inline了,每个SquareMatrix::invert的实例都会得到SquareMatrixBase::invert代码的一份拷贝,你会发现你有回到了对象代码重复的原点。详见条款30)

SquareMatrixBase::invert只被用来在防止派生类代码重复,所以是protected而不是public的。
调用它的额外开销应该是0,因为派生类的inverts调用基类版本使用了inline函数。同时这里使用了this->(如上一条款所说,防止模板化基类被衍生类掩盖)
同时注意SquareMatrix和SquareMarixBase之间的继承是private的。
反映出一个事实:使用基类的原因只是帮助派生类的实现,并非表达出SquareMatrix和SquareMatrixBase之间的“is-a”关系。

b.派生类如何告知基类数据在哪里
还有一个问题要解决,SquareMatrixBase::invert如何获取matrixSize,现在只有派生类知道。派生类如何同基类进行通讯才能让基类执行invert?
一个可能的方法是向SquareMatrixBase::invert函数中添加另外一个参数,可能是一个指向矩阵数据的指针,但是如果invert不是存在于SquareMatrix中的能够以独立于size的方式重写并且移入SquareMatrixBase中的唯一函数。我们需要为所有的函数添加一个额外的参数,但是如此以来我们就重复告诉了SquareMatrixBase同样的信息。

一个替换方法是让SquareMatrixBase存储一个指向存放矩形数据的内存的指针代替函数,改进如下,

template
class SquareMatrixBase {
protected:
SquareMatrixBase(std::size_t n, T *pMem): size(n), pData(pMem){} //存储矩阵大小和指针指向矩阵
void setDataPtr(T *ptr) { pData = ptr;} //赋值给pData保存
private:
std::size_t size;
T *pData;
};

由衍生类储存和决定内存分配方式,可以在衍生类SquareMatrix对象内部存储矩形数据。
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase {
public:
SquareMatrix(): SquareMatrixBase(n, data) {} //为基类传递数据
private:
T data[nn];
};
这种类型的对象没有必要做动态内存分配,但是对象本身可能会非常大。一个替换的方法是为每个矩形在堆上存放数据(也就是new出来),
template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase {
public:
SquareMatrix():
SquareMatrixBase(n, NULL), //初始化基类中的指针
pData(new T[n
n]){ this->setDataPtr(pData.get());} //创建内存并传递给基类指针
private:
boost::scoped_array pData;
};

不管将数据存放在哪里,从代码膨胀的角度来说,关键结果是现在很多(可能是所有的)SquareMatrix的成员函数可以简单的以inline调用基类的(non-inline)函数,基类函数由持有相同类型元素数据的矩形共享。
不同size的SquareMatrix对象属于不同类型。所以即使(SquareMatrix<double,5>和SquareMatrix<double,10>对象在SquareMatrixBase中)使用相同的成员函数,已不能把一个SquareMatrix<double,5>对象传给一个需要SquareMatrix<double,10>的函数。
这样有优点但也有缺点。
缺点是矩形size大小固定的invert版本比共享invert版本可能产生更好的代码。例如,在前一种版本中,sizes是编译期常量,因此是常量传播最优化,包括也可以把其放入生成指令中作为直接操作数。这在后一种版本中无法做到。
为不同size的矩阵只提供一个invert版本可以减小可执行程序的大小,这能减少程序的工作集大小,并且能够强化指令高速缓存的引用集中化。这些东西能够使得程序运行速度更快,并且相对size指定的版本才能做出的优化,它可能会做出更好的补偿。哪种方法效果更好?唯一的方法是两种方法都试一下,在你的特定平台和有代表性的数据集上观察它们的行为。
优点是后一种版本中执行文件大小减少,降低working set,并强化指令高速缓存区的引用集中化,使程序更快。所以,孰优孰劣?
另外一个有关效率的需要考虑的地方是有关对象的大小。将size大小无关的版本向上移动到基类中,这会增加每个对象的大小。例如,每个SquareMatrix对象有一个指向SquareMatrixBase类中数据的指针。即使每个派生类中已经有取得数据的方法,这也为每个SquareMatrix对象至少增加一个指针的大小。我们可以修改设计来去掉指针,但是这也是需要付出代价的。它同样能导致资源管理问题:如果基类存储了指向矩阵数据的指针,但是数据既有可能是动态分配的也可能存储在派生类对象中(正如我们看到的),如何决定是不是需要delete指针?这样的问题是有答案的,但是你做的越精细事情就变得越复杂。从某种意义上讲,有一点代码重复开始开起来有点幸运了。
其实以上这一大段都是说消除非类型模板参数导致的代码膨胀是有两面性,需要具体问题具体分析。

问题3.如何处理类型模板参数导致的代码膨胀
这个条款仅仅讨论了由于非类型模板参数导致的代码膨胀,其实类型参数同样可以导致代码膨胀。例如,在许多平台中,int和long有着相同的二进制表示,所以在成员函数中使用vector和vector看起来会一样,这正是代码膨胀的定义。一些连接器会把相同的代码实现整合到一起,但是有一些不会,这就意味着由模板实例化的int和long版本会在一些环境中导致代码膨胀。类似的,在大多数平台上,所有的指针类型有着相同的二进制表示,所以带指针类型的模板(例如,list<int*>,list<const*>,list<SquareMatrix<long,3>>等等)应该通常能够为每个成员函数使用一个单一的底层实现。特别的,这就意味着实现一个强类型指针(T 指针)的成员函数时,让它们调用一个无类型指针的函数(void*指针)。一些标准C++库的实现为模板就是这么做的(如vector,deque,和list)。如果你关心在你的模板中出现的代码膨胀问题,你可能就会想开发出做相同事情的模板。

请记住的原文摘录:
1)模板会产生多个类和多个函数,所以任何模板代码都不应该与某个造成膨胀的模板参数产生相依关系。
2)因非类型模板参数导致的代码膨胀,往往可消除,做法是以函数参数或者类成员变量代替模板参数。
3)因类型参数导致的代码膨胀,往往可降低,方式是让带有完全相同二进制表述(binary representations)的具现类型(instantiation types)共享。

5.运用成员函数模板接受所有兼容类型

本条款涉及大量智能指针内容,请先看相关条款。智能指针(smart pointer)是行为像指针的对象,并提供了指针没有的机能。STL容器中的迭代器基本上都是智能指针。因此你不能通过使用“++”来将链表中的指向一个节点的内建指针移到下一个节点上去,但是list::iterator可以这么做。

a.智能指针的隐式转换问题

指针好的一点是支持隐式转换(implicit conversions)。例如:派生类指针可以隐式转换为基类指针,指向非const的指针可以隐式转换成为指向const对象的指针。例如:
class Top
class Middle: public Top
class Bottom: public Middle
Top pt1 = new Middle; // convert Middle Top*
Top pt2 = new Bottom; // convert Bottom Top*
const Top pct2 = pt1; // convert Top const Top*

自定义的智能指针中模仿这种转换是很微妙的。我们想让下面的代码通过编译:
template
class SmartPtr {
explicit SmartPtr(T *realPtr); // 智能指针一般以内置指针初始化
};
SmartPtr pt1 = SmartPtr(new Middle); // convert SmartPtr SmartPtr
SmartPtr pt2 = SmartPtr(new Bottom); // convert SmartPtr SmartPtr
SmartPtr pct2 = pt1; // convert SmartPtr SmartPtr

explicit可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。同一个模板的不同实例(具现体instantiations)之间没有固有的关系(如有继承关系的两类分别具现化一个模板,产生的两个具现体没有了继承关系),所以编译器将SmartPtr和SmartPtr视为完全不同的类。为了实现SmartPtr类之间的转换,必须明确编写出来。

我们可以用一个SmartPtr或一个SmartPtr来构造一个SmartPtr,但是如果这个继承体系在未来扩展了,SmartPtr对象必须能够从其他智能指针类型中构造出来。例如,假设我们增加了下面的类:
class BelowBottom: public Bottom;
我们需要支持用SmartPtr对象来创建SmartPtr对象,这需要修改SmartPtr模板,并不好。
因为这样所需要的构造函数的数量是没有限制的。既然模板可以被实例化成为没有限制数量的函数,因此需要的是一个构造函数模板。这样的模板是成员函数模板(member function templates)(或member templates)。

b. 使用成员函数模板泛化拷贝构造函数进行隐式转换

template
class SmartPtr {
template // 成员模板
SmartPtr(const SmartPtr& other); //生成泛化拷贝构造函数
};

每个类型T和每个类型U,SmartPtr能够用SmartPtr创造出来,因为SmartPtr有一个以SmartPtr作为参数的构造函数 。像这样的构造函数用u对象来创建t对象,两个对象来自于相同的模板但是它们为不同具现体,它通常被叫做泛化拷贝构造函数(generalized copy constructors)。

泛化拷贝构造函数不能声明为explicit。内建指针类型之间的类型转换(例如从派生类转换到基类指针)是隐式的,因此智能指针模仿这种行为就是合理的。在模板化的构造函数上省略explicit正好因为这个。

泛化拷贝构造函数提供了更多的东西。我们想要用SmartPtr创建SmartPtr,但是我们不想用SmartPtr创建SmartPtr,因为这违背了public继承的含义。我们同样不想用SmartPtr创建SmartPtr,因为没有从double到int之间的隐式转换。我们必须将成员模板生成的这种成员函数集合剔除掉。

假设SmartPtr遵循auto_ptr和tr1::shared_ptr的设计,也提供一个get成员函数来返回智能指针对象所包含的内建类型指针的一份拷贝,我们可以使用构造函数模板的实现来对一些转换进行限制,我们改进下:

template
class SmartPtr {
public:
template
SmartPtr(const SmartPtr& other)
: heldPtr(other.get()){} //用U初始化T
T* get() const { return heldPtr;}
private:
T *heldPtr; //内置指针
}

用SmartPtr中包含的类型为U的指针来初始化SmartPtr中的类型为T的数据成员。这只有在能够从U指针到T指针进行隐式转换的情况下才能通过,现在SmartPtr有了一个泛化拷贝构造函数,只有传递的参数为兼容类型时才能够通过编译。

c. 成员函数模板对赋值的支持

成员函数模板的使用不限定在构造函数上。

template class shared_ptr {
public:
template
explicit shared_ptr(Y * p);
template
shared_ptr(shared_ptr const& r);
template
explicit shared_ptr(weak_ptr const& r);
template
explicit shared_ptr(auto_ptr& r);
template // assign from
shared_ptr& operator=(shared_ptr const& r);
template // shared_ptr or
shared_ptr& operator=(auto_ptr& r);
};

除了泛化拷贝构造函数的这些构造函数都是explicit的。这就意味着从shared_ptr的一种类型隐式转换到shared_ptr的另一种类型是允许的,但是内建类型指针和其他的智能指针类型到shared_ptr的隐式转换是禁止的。传递给tr1::shared_ptr构造函数和赋值运算符的auto_ptr没有被声明为const,但是tr1::shared_ptr和tr1::weak_ptr的传递却声明为const了,这是因为auto_ptr被拷贝的时候已经被修改了。

d. 成员函数模板会生成默认拷贝构造函数

编译器会自动生成的4个成员函数中的两个函数为拷贝构造函数和拷贝赋值运算符。成员模板没有修改语言的规则,因此如果你需要一个拷贝构造函数而你没有自己声明,编译器会为你生成一个。在一个类中声明一个泛化拷贝构造函数不会阻止编译器生成它们自己的拷贝构造函数,所以如果你想控制拷贝构造函数的所有方面,你必须同时声明一个泛化拷贝构造函数和“普通的”构造函数,例如,

template class shared_ptr {
public:
shared_ptr(shared_ptr const& r); //拷贝构造函数

template
shared_ptr(shared_ptr const& r); //泛化拷贝构造函数

shared_ptr& operator=(shared_ptr const& r); //拷贝赋值运算符

template
shared_ptr& operator=(shared_ptr const& r); //泛化拷贝赋值运算符
};

请记住的原文摘录:
1)使用成员函数模板(member function tempates)来生成可接受所有兼容类型的函数。
2)如果你声明成员模板用于泛化拷贝构造函数和泛化赋值运算符,你同样需要声明普通的拷贝构造函数和拷贝赋值运算符。

6.需要类型转换时请为模板定义为非成员函数

a. 模板化operator*

本条款主要介绍模板如何支持类型转换。条款24中解释了为什么只有非成员函数有能力对于所有参数的隐式类型转换,并且使用了一个为Rational 类创建的operator函数作为实例。在继续之前建议你先回顾一下这个例子,因为这个条款的讨论是对它的扩展,对Rational和opeartor同时进行模板化:

template
class Rational {
public:
Rational(const T& numerator = 0, //为什么引用传递看条款20
const T& denominator = 1);

const T numerator() const; //为什么返回值值传递看条款28

const T denominator() const; //为什么是const看条款3
};

template
const Rational operator*(const Rational& lhs, const Rational& rhs){}

现在我们想用模板支持混合模式的算术运算:

Rational oneHalf(1, 2);
Rational result = oneHalf * 2; //编译error

b. 模板参数不能隐式转换

上面的代码不会通过编译,表明了模板化的Rational和非模板版本有些地方还是不一样的。在条款24中,编译器知道我们尝试调用什么函数(带两个Rational参数的operator*),但是这里,编译器不知道我们想要调用哪个函数。因为具现化之前,需要算出T是什么。
为了T类型,看operator调用时传递的实参数类型,两个参数类型分别是Rational和int。
第一个参数推导很容易,operator
的第一个参数所需要的类型为Rational,实际上这里传递给operator的第一个参数的类型是Rational,所以T必须为int。operator的第二个参数所需要的类型也为Rational,但是传递给operator*的第二个参数是一个int值,在这种情况下编译器该如何确认T是什么呢?因为在模板参数演绎期间永远不会考虑使用隐式类型转换函数。这样的转换是在函数调用期间被使用的,所以在你调用一个函数之前,你必须知道哪个函数是存在的。为了知道这些,你就必须为相关的函数模板演绎出参数类型,但是在模板参数演绎期间不会通过调用构造函数来进行隐式转换,因而模板实参推导是一个问题。

c.使用友元函数解决编译问题

模板类中的一个友元声明能够引用一个实例化函数。类Ration能够为Ration声明一个友元函数的operator*。类模板不再依赖于模板参数演绎(这个过程只应用于函数模板),所以T在类Ration被实例化的时候就能被确认。所以声明一个合适的友元operator*函数能简化整个问题:

template
class Rational {
public:
friend
const Rational operator*(const Rational& lhs,const Rational& rhs);
};

template
const Rational operator*(const Rational& lhs,const Rational& rhs){}

现在能通过编译了,因为当对象第一个参数被声明为类型Rational的时候,Rational被实例化出来了,参数为Rational的友元函数operator*被自动声明。作为一个被声明的函数(非函数模板),编译器在调用它时就能够使用隐式类型转换函数(像Rational的非显示non-explicit构造函数),这就是如何使混合模式调用成功。

但是现在还不能链接成功。在一个类模板中,模板的名字能够被用来当作模板和模板参数的速写符号,所以在Rational中,我们可以写成Rational来代替Rational。在这个例子中只为我们的输入减少了几个字符,但是如果有多个参数或者更长的参数名字的时候,它既能减少输入也能使代码看起来更清晰。我提出这些是因为在上面的例子中operator的声明用Rational作为参数和返回值,而不是Rational。下面的声明效果是一样的:
template
class Rational {
public:
friend
const Rational operator
(const Rational& lhs,const Rational& rhs);
};

回到链接问题。混合模式的代码能够通过编译,因为编译器知道我们想调用一个实例化函数(带两个Rational参数的operator函数),但是这个函数只在Rational内部进行声明,而不是被定义。我们并没有提供一个定义,这就是为什么连接器不能知道函数定义的原因。一个解决方案是将operator函数体合并到声明中:

template
class Rational {
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}
};

确实这能够工作,友元函数并没有被用来访问类的非public部分(friend传统用途)。为了使所有参数间的类型转换成为可能,我们需要一个非成员函数,为了让函数被自动具现化出来,我们需要在类内部声明一个函数。在类内部声明一个非成员函数的唯一方法是将其声明为友元函数。

d.模板友元函数inline

在类内部定义的函数被隐式的声明为inline函数,这同样包含像operator这样的友元函数。你可以最小化这种inline声明的影响,通过让operator只调用一个定义在类体外的辅助函数。在这例子中没有必要这么做,因为operator已经被实现成了只有一行的函数,对于更加复杂的函数体值得。
Rationl是模板的事实意味着辅助函数通常情况下也会是一个模板,所以在头文件中定义Rational的代码会像下面这个样子:
template class Rational;
template
const Rational doMultiply(const Rational& lhs,const Rational& rhs);
template
class Rational {
public:
friend
const Rational operator
(const Rational& lhs,const Rational& rhs){ return doMultiply(lhs, rhs); }
};

许多编译器从根本上强制你将所有的模板定义放在头文件中,所以你可能同样需要在你的头文件中定义doMultiply:
template
const Rational doMultiply(const Rational& lhs,const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}

doMultiply作为一个模板不支持混合模式的乘法,但是也不需要支持。它只被operator调用,operator支持混合模式操作就够了!从根本上来说,函数operator*支持必要的类型转换,以确保两个Rational对象被相乘,然后它将这两个对象传递到doMultiply模板的合适实例中进行实际的乘法操作。

解释一下上面的两个问题:
1.为什么上面例子中即使定义成非成员函数,oneHalf * 2也仍然编译不过(条款24的普通函数例子是可以编译通过,可以结合看一下)?
因为与条款24的普通函数例子不同,此处是模板函数。模板参数演绎期间永远不会考虑使用隐式类型转换函数,即使是非成员函数,所以编译不过。
2.使用友元为什么解决了编译问题?
因为当oneHalf被声明为类型Rational的时候,具现化出Rational类,同时友元函数operator*也被声明出来(模板具现化后其函数也不再是一个函数模板,而是一个函数),所以参数演绎可以进行隐式类型转换函数,并编译通过。

以上解决模板类型转换编译问题有两个关键点:一是使用友元让实例出的函数代替模板函数进行参数演绎,二是使用非成员函数进行参数隐式类型转换。

请记住的原文摘录:
1)当我们编写一个类模板,而它所提供的之于此模板相关的函数支持所有参数之隐式类型转换时,请将那些函数定义为类模板内部的友元函数。

7.请使用traits classes表现类型信息

在C++中,traits classes(特性类)习惯上总是被实现为struct,Traits classes的作用主要是用来为使用者提供类型信息。
在STL中,容器与算法是分开的彼此独立设计,容器与算法之间通过迭代器联系在一起。算法是如何从迭代器类中萃取出容器元素的类型的?没错正是traits classes的功能。
STL主要由用以表现容器,迭代器和算法创建的模板组成,以及一些工具模板。

advance正是将迭代器移动指定的距离工具模板:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d); //移动d单位
advance仅仅做了iter+=d,但是advance并不能用这种方式实现的,因为只有随机访问(random access)迭代器支持+=操作。其他迭代器类型必须反复的++或者–d次。

a. 五种迭代器

总共有5类STL迭代器,对应五种操作:
输入迭代器(input iterator)只能向前移动,一次一步,只读指向内容,并且只能读一次,模拟输入文件的读指针(read pointer),如C++库的istream_iterator。

输出迭代器(output iterator)类似,区别是只写指向内容,并且只能写一次,模拟的是输出文件的写指针,如ostream_iterator。这是两类功能最弱的迭代器。因为输入和输出迭代器只能向前移动,只能读写一次,只适合one-pass algorithms(一次性操作算法)。

前向迭代器(forward iterator),能做输入和输出迭代器能做的所有事情,并且能多次的读写。这就使得它们能被multi-pass algorithms(多次性操作算法)所使用。STL没有提供单向链表,但是一些库却提供了(通常叫做slist),这种容器中的迭代器为前向迭代器。TR1中的哈希容器迭代器也可能是前向迭代器(取决版本)。

双向迭代器(bidirectional iterators)和前向迭代器相比添加了向后移动的能力。STL中的list提供的迭代器就属于这种类别,set,multiset,map和multimap也是这种类别。

随机访问迭代器(random access iterator)和双向迭代器相比添加了迭代器运算(iterator arithmetic),也就是在常量时间内向前或者向后跳跃任意的距离。这种运算同指针运算类似,因为随机访问迭代器模拟的就是内建类型的指针,内建类型指针也可以被当做随机访问迭代器。Vector,deque和string迭代器都是随机访问迭代器。

为了识别这五种迭代器类型,C++在标准库提供了卷标结构(tag struct)识别:
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 {};

这些结构体之间的继承关系是有效的“is-a”关系:比如所有的前向迭代器同样是输入迭代器。

b. advance简析

考虑到不同的迭代器功能,实现advance的一种方法是对迭代器进行反复加或者减,这个方法会花费线性的时间。随机访问迭代器支持常量时间的迭代器算法,在我们需要的时候会使用:
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; }
}
}

这需要决定iter是不是一个随机访问迭代器。trait允许你在编译期间获取一个类型。

c. Traits分析

Traits是一种技术,使用这项技术的一个要求是它必须使内建类型同用户自定义类型一样工作。类型的traits信息必须位于类型之外把他放进一个模板及特化版本中。对于迭代器来说,标准库中的模板被命名为iterator_traits:
template
struct iterator_traits; //迭代器分类信息
iterator_traits通常是一个struct,又被称为trits classes。
iterator_traits运作方式是在结构体iterator_traits中声明一个叫做iterator_category的typedef。这个typedef唯一确认了IterT的迭代器类别。
它强制自定义的迭代器类型必须包含叫做iterator_category的内嵌typedef,用来确认卷标结构(tag struct)。例如针对deque迭代器(可随机访问)的class:

template …
class deque {
class iterator {
typedef random_access_iterator_tag iterator_category;
};
};

List的迭代器是双向的方式:
template < … >
class list {
class iterator {
typedef bidirectional_iterator_tag iterator_category;
};
};

iterator_traits只是使用iterator class的typedef:
template
struct iterator_traits {
typedef typename IterT::iterator_category iterator_category;
};

这支持自定义类型,但是指针中没有内嵌的typedef,所以接下来需要处理指针迭代器。
iterator_traits为指针类型提供了偏特化版本。
指针的行为表现同随机访问迭代器类似,所以:
template
struct iterator_traits<T*>
{
typedef random_access_iterator_tag iterator_category;
};

设计实现traits class:
1)确认你想要支持的类型的一些信息(例如对于迭代器是迭代器类别category)。
2)为了确认信息,选择一个名字(例如iterator_category)
3)为你想支持的类型提供一个模板和一组特化版本(例如iterator_traits)。

d. 使用traits class实现advance

我们真正想要的是为类型提供一个能够在编译期进行判断的条件语句,C++已经有一种方法来实现这种行为即重载。当你重载某个函数f的时候,你为不同的重载函数指定不同的参数类型。当你调用f时,编译器会根据你所传递的参数选择最佳匹配重载函数。这是针对类型的编译期条件结构。为了让advance表现出我们想要的行为,所有我们必须要做的是重载的多个版本,它们包含了advance的本质内容,每个函数接受不同类型的iterator_category对象。我将这些函数命名为doAdvance:

template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag)
{
iter += d;
}

template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag)
{
if (d >= 0) { while (d–) ++iter; }
else { while (d++) --iter; }
}

template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
{
if (d < 0 ) {throw std:: out_of_range(“Negative distance”);}
while (d–) ++iter;
}

forward_iterator_tag继承自input_iterator_tag,因而上面的input_iterator_tag版本同样能够处理forward迭代器。
对于随机访问迭代器和双向迭代器来说,同时能够做正向或者负向的移动,但是对于forward迭代器或者input迭代器来说,如果你想进行一个负向的移动就会出现不明确行为。假设d是非负的,当传递一个负参数时,会进入一个很长的循环中,直到d变为0为止。它的替代方法是抛出一个异常。

考虑为doAdvance所重载的不同版本,所有advance需要做的就是调用它们,传递一个额外的合适的迭代器类别对象:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance(iter, d, typename std::iterator_traits::iterator_category());
}

我们可以总结一下如何使用traits class:
1)创建一组重载的函数或者函数模板(例如doAdvance),通过traits参数区分。函数的实现与接收的信息对应。
2)创建一个控制函数或者函数模板(例如advance)调用上面的函数,并将traits class提供的信息传递进去。

Traits被广泛使用在标准库中。除了iterator_category,还为迭代器提供了四种其它的信息(最有用的就是value_type)还有char_traits(存储了字符类型的信息),numeric_limits(提供数字类型信息),例如某数值能够表示的最大和最小值等等。
traits classes包括is_fundamental(判断T是否为内建类型),is_array(判断T是否为数组),和is_base_of<T1,T2>(判断T1和T2是否相同或者T1是T2的基类)。

请记住的原文摘录:
1)Traits classes使得类型相关信息在编译期可用。它们以模板和模板特化版本来进行实现。
2)整合重载技术(overloading)后,traits classes有可能在编译期对类型执行if-else测试。

8.认识template元编程

本条款应该至少是模板这一章最难的一部分,并且书上介绍的也很简洁,在知乎上有人说TMP是C++最高级黑魔法,哈哈,很诱惑人呦,涉及的概念很多反正我现在短时间是弄不懂,只能先拷一下书上的一些概念。
模板元编程(template metaprogramming 也就是TMP)是实现基于模板的C++程序的过程,它能够在编译期执行。一个TMP是用C++实现的并且可以在C++编译器内部运行的一个程序。
是一种元编程技术,编译器使用模板产生暂时性的源码,然后再和剩下的源码混合并编译。这些模板的输出包括编译时期常量、数据结构以及完整的函数。如此利用模板可以被想成编译期的运行(来自百度百科)。
元编程这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作,一门语言同时也是自身的元语言的能力称之为反射。
给我的感觉我们通过框架解析xml生成资源对象可能也属于元编程(只是我自己猜的)。

C++不是为了模板元编程而设计的,但是自从TMP早在1990年被发现之后,它就被证明是非常有用的,为了使TMP的使用更加容易,在C++语言和标准库中加入了一些扩展。TMP是被发现的,而不是被发明。当模板被添加到C++中的时候TMP这个特性就被引入了。
TMP有两种强大的力量。第一,它使得一些事情变得容易,也即是说如果没有TMP,这些事情做起来很难或者不可能实现。第二,因为模板元编程在C++编译期执行,它们可以将一些工作从运行时移动到编译期。一个结果就是一些原来通常在运行时能够被发现的错误,现在在编译期就能够被发现了。另外一个结果就是使用TMP的C++程序在基本上每个方面都更加高效:更小的执行体,更短的运行时间,更少的内存需求。(然而,将工作从运行时移到编译期的一个后果就是编译时间增加了。使用TMP的程序比没有使用TMP的程序可能消耗更长的时间来进行编译。)

advance的伪代码:
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; }
}
}

我们可以使用typeid替换伪代码,让程序能够执行。这就产生了一个“普通的”C++方法——也就是所有工作都在运行时开展的方法:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if ( typeid(typename std::iterator_traits::iterator_category) == typeid(std::random_access_iterator_tag)) {
iter += d;
}
else
{
if (d >= 0) { while (d–) ++iter; }else { while (d++) --iter; }
}
}

基于typeid的方法比使用trait效率更低,因为通过使用这种方法,(1)类型测试发生在运行时而不是编译期(2)执行运行时类型测试的代码在运行的时候必须可见。事实上,这个例子也展示出了为什么TMP比一个“普通的”C++程序更加高效,因为traits方式属于TMP。记住,trait使得在类型上进行编译期if…else运算成为可能。
看下面的例子:
std::list::iterator iter;
advance(iter, 10);
考虑为上面调用所产生的advance的版本,将模板参数IterT和DistT替换为iter和10的类型之后,我们得到下面的代码:
void advance(std::list::iterator& iter, int d)
{
if (typeid(std::iterator_traits<std::list::iterator>::iterator_category) ==typeid(std::random_access_iterator_tag))
{
iter += d;
}
else
{
if (d >= 0) { while (d–) ++iter; }else { while (d++) --iter; }
}
}

有问题的是使用+=的语句,在这个例子中,我们在list::iterator上使用+=,但是list::iterator是一个双向迭代器,所以它不支持+=。只有随机访问迭代器支持+=。现在,我们知道了+=这一行将永远不会被执行到,因为为list::iteraotr执行的typeid测试永远都不会为真,但是编译器有责任确保所有的源码都是有效的,即使不被执行到,当iter不是随机访问迭代器“iter+=d”就是无效代码。将它同基于tratis的TMP解决方案进行比较,后者把为不同类型实现的代码分别放到了不同的函数中,每个函数中进行的操作只针对特定的类型。

TMP已经被证明是图灵完全的(Turing-Complete),这也就意味着它足够强大到可以计算任何东西。使用TMP,你可以声明变量,执行循环,实现和调用函数等等。但是这些概念同“普通”C++相对应的部分看起来非常不同。例如,条款47中if…else条件在TMP中是如何通过使用模板和模板特化来表现的。但这是程序级别(assembly-level)的TMP。TMP库(例如,Boost MPL,见Item 55)提供了更高级别的语法,这些语法不会让你误认为是“普通的”C++。

再瞥一眼事情在TMP中是如何工作的,让我们看一下循环。TMP中没有真正的循环的概念,所以循环的效果是通过递归来完成的。(如果一提到递归你就不舒服,在进入TMP 冒险之前你就需要处理好它。TMP主要是一个函数式语言,递归对于函数式语言就如同电视对美国流行文化一样重要:它们是不可分割的。)即使是递归也不是普通的递归,因为TMP循环没有涉及到递归函数调用,所涉及到的是递归模板实例化(template instantiations)。

TMP的“hello world”程序是在编译期计算阶乘。它算不上是令人激动的程序,“hello world”也不是,但是这两个例子对于介绍语言都是有帮助的。TMP阶乘计算通过对模板实例进行递归来对循环进行示范。也同样示范了变量是如何在TMP中被创建和使用的,看下面的代码:
template
struct Factorial {
enum { value = n * Factorial::value };
};
template<>
struct Factorial<0> {
enum { value = 1 };
};

考虑上面的模板元编程,你通过引用Factorial::value来得到factorial(n)的值。
代码的循环部分发生在模板实例Factorial引用模板实例Factorial的时候。像所有递归一样,有一种特殊情况来让递归终止。在这里是模板特化Factorial<0>。
每个Factorial模板的实例都是一个结构体,每个结构体使用enum hack来声明一个叫做value的TMP变量。
Value持有递归计算的当前值。如果TMP有一个真正的循环结构,value将会每次循环的时候进行更新。既然TMP使用递归模板实例来替换循环,每个实例会得到它自己的value的拷贝,每个拷贝都会有一个和“循环”中位置想对应的合适的值。
int main()
{
std::cout << Factorial<5>::value;
std::cout << Factorial<10>::value;
}

如果模板和特化,递归实例和enum hacks,还有像Factorial::value这样的输入使你毛骨悚然,你还是一个“普通的”C++程序员。
Factorial对TMP的功能进行了示范,如同“hello world”程序对任何传统编程语言的功能进行示范一样。为了让你明白为什么TMP是值得了解的,知道它能够做什么很重要,这里有三个例子:
确保因次单位(dimensional unit)的正确性。在科学和工程应用中,把因次单位(例如,质量,距离和时间)正确的拼到一起是很必要的。将表示质量的变量赋值给表示速度的变量是错误的,但是用距离变量除以时间变量然后将结果赋值被速度变量就没有问题。通过使用TMP,确保(在编译期间)程序中的所有因次单元组合的正确性就是可能的,不管计算有多复杂。(这也是使用TMP来侦测早期错误的一个例子。)TMP这种用法的一个有趣的方面是它能够支持分数因次的指数。这需要在编译期间将分数简化,然后编译器才能够确认,例如,单元 time的1/2次幂同time的4/8次幂是相同的。
优化矩阵操作。Item 21中解释了有一些函数(包括 operator*)必须返回新的对象,考虑下面的代码:

typedef SquareMatrix<double, 10000> BigMatrix;
BigMatrix m1, m2, m3, m4, m5;
BigMatrix result = m1 * m2 * m3 * m4 * m5;

用“普通的”方式来计算result会有四次创建临时matrice对象的调用,每次调用都应用在对operator*调用的返回值上面。这些独立的乘法在矩阵元素上产生了四次循环。使用TMP的高级模板技术——表达式模板(expression templates),来消除临时对象以及合并循环是有可能的,并且不用修改上面的客户端代码的语法。最后的程序使用了更少的内存,而且运行速度会有很大的提升。
产生个性化的设计模式实现。像策略模式,观察者模式,访问者模式等等这些设计模式能够以很多方式被实现。使用基于模板的技术被叫做policy-based设计,我们可以创建表示独立设计选择(choice或者叫”policies”)的 模板,这些模板可以以任意的方式进行组合来产生个性化的模式实现。例如,使用这种技术能够创建一些实现智能指针行为策略(policies)的模板,使用它能够产生(在编译期)上百种不同的智能指针类型。这项技术已经超越了编程工艺领域,如设计模式和智能指针,它成为了生殖编程(generative programming)的基础。

TMP并不是为每个人准备的。因为语法不直观,支持的相关工具也很弱。(像为模板元编程提供的调试器。)作为一个“突然性“的语言它只是最近才被发现的,TMP编程的一些约定正在实验阶段。然而通过将工作从运行时移到编译期所带来的效率提升带给人很深刻的印象,对一些行为表达的能力(很难或者不可能在运行时实现)也是很吸引人的。
对于TMP的支持正在上升期。很可能下个版本的C++就是显示的支持它。TR1中已经支持了。关于这个主题的书籍已经开始出来了,网上的一些关于TMP信息也越来越多。TMP可能永远不会成为主流,但是对于一些程序员来说——尤其是程序库的实现者——几乎必然会成为主要手段。

请记住的原文摘录:
1)模板元编程可以将工作从运行时移到编译期,因而得以实现早期错误侦测和更高的执行效率。
2)模板元编程可被用来生成基于政策选择组合(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值