C++ template机制自身是一部完整的图灵机(Turing-complete):它可以被用来计算任何可计算的值。于是导出了模板元编程(TMP, template metaprogramming),创造出“在C++编译器内执行并于编译完成时停止执行”的程序。

 

41:了解隐式接口和编译期多态

所谓显式接口(explicit interface),是指在源码中明确可见的接口,显式接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成。而运行时多态,就是指继承和virtual带来的动态绑定机制。

在Templates及泛型编程的世界里,隐式接口和编译期多态更重要一些。比如下面的模板定义:

template<typename T>
void doProcessing(T& w)
{
if (w.size() > && w != someNastyWidget) {
T temp(w);
temp.normalize();
temp.swap(w);
}
}

w必须支持哪一种接口,是由template中执行于w身上的操作来决定。本例看来w的类型T好像必须支持size,normalize和swap成员函数、copy构造函数、不等比较。这一组表达式便是T必须支持的一组隐式接口(implicit interface)。

凡涉及w的任何函数调用,例如operator>和operator!=,有可能造成template具现化(instantiated),使这些调用得以成功。这样的具现行为发生在编译期。“以不同的template参数具现化function templates”会导致调用不同的函数,这便是所谓的编译期多态。

42:了解typename的双重意义

以下template声明式中,class和typename的意义是完全相同的,但更推荐typename:

template<class T> class Widget;                 // uses "class"
template<typename T> class Widget; // uses "typename"        

但是,某种情况下必须使用typename。首先看下面的函数:

template<typename C>
void print2nd(const C& container)
{ // this is not valid C++!
if (container.size() >= ) {
C::const_iterator iter(container.begin()); // get iterator to 1st element
++iter; // move iter to 2nd element
int value = *iter; // copy that element to an int
std::cout << value; // print the int
}
}

在上面的函数模板中,iter的类型是C::const_iterator,而实际是什么必须取决于template参数C。如果 template内出现的名称相依于某个template参数,称之为从属名称(dependent names)。如果从属名称在class内呈嵌套状,我们称它为嵌套从属名称(nested dependent name )。C::const_iterator就是这样一个名称。实际上它还是个嵌套从属类型名称(nested dendent type name ),也就是个嵌套从属名称并且指涉某类型。

另一个local变量value,其类型是int。int是一个并不倚赖任何template参数的名称。这样的名称是谓非从属名称(non-dependent names)。

嵌套从属名称有可能导致解析困难。比如:

template<typename C>
void print2nd(const C& container)
{
C::const_iterator * x;
...
}

看上去好像是我们将 x 声明为一个指向 C::const_iterator 的局部变量。但编译器不这么认为,比如如果 C 有一个静态数据成员碰巧就叫做 const_iterator 呢?而且 x 碰巧是一个全局变量的名字呢?在这种情况下,上面的代码就不是声明一个 局部变量,而成为 C::const_iterator 乘以 x!

直到 C 成为已知之前,没有任何办法知道 C::const_iterator 到底是不是一个类型。C++ 有一条规则解决这个歧义:如果解析器在一个模板中遇到一个嵌套从属名字,它假定那个名字不是一个类型,除非你明确告诉它。所以,一开始的print2nd中的语句并不合法:

C::const_iterator iter(container.begin());   

iter 的 声明仅在 C::const_iterator 是一个类型时才有意义,但是我们没有告诉 C++ 它是,所以C++ 就假定它不是。要想纠正这个错误,必须明确指出C::const_iterator 是一个类型:这就必须使用typename:

template<typename C>                           // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= ) {
typename C::const_iterator iter(container.begin());
...
}
}

一般性规则很简单:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧临它的前一个位置放上关键字typename。

typename只被用来验明嵌套从属类型名称,其他名称不该有它存在。例如:

template<typename C>                   // typename allowed (as is "class")
void f(const C& container, // typename not allowed
typename C::iterator iter); // typename required

上述的C并不是嵌套从属类型名称(它并非嵌套于任何“取决于template参数”的东西内),所以声明container时并不需要以typename为前导,但C::iterator是个嵌套从属类型名称,所以必须以typename为前导。

上面的规则有个例外:typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在成员初始化列表中作为base class修饰符。例如:

template<typename T>
class Derived: public Base<T>::Nested { // base class list: typename not allowed
public:
explicit Derived(int x)
: Base<T>::Nested(x) // base class identifier in mem
{ // init. list: typename not allowed typename Base<T>::Nested temp; // use of nested dependent type
... // name not in a base class list or
} // as a base class identifier in a
... // mem. init. list: typename required
};

来看最后一个例子:

template<typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}

上面的函数以迭代器为参数,函数第一条语句的意思是为该迭代器所指向的对象创建一个副本。std::iterator_traits<IterT>::value_type 就是表示IterT所指对象的类型。比如如果 IterT 是 vector<int>::iterator,则temp 就是 int 类型。

std::iterator_traits<IterT>::value_type是一个嵌套从属类型名称(value_type 嵌套在 iterator_traits<IterT>内部,而且 IterT 是一个 模板参数),所以必须在它之前放置 typename。

43:学习处理模板化基类内的名称

看一下下面的代码,它表示需要将信息发到若干不同的公司,信息要么是明文,要么是密文:

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);
...
};
... // classes for other companies class MsgInfo { ... }; // class for holding information
// used to create a message
template<typename Company>
class MsgSender {
public:
... // ctors, dtor, etc.
void sendClear(const MsgInfo& info)
{
std::string msg;
create msg from info; Company c;
c.sendCleartext(msg);
} void sendSecret(const MsgInfo& info) // similar to sendClear, except
{ ... } // calls c.sendEncrypted
};

现在假设有了新的需求,需要在每次发送明文是记录日志。此时可以通过继承实现:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
... // ctors, dtor, etc.
void sendClearMsg(const MsgInfo& info)
{
//write "before sending" info to the log; sendClear(info); // call base class function;
// this code will not compile!
//write "after sending" info to the log;
}
...
};

上面的代码无法通过编译,编译器会抱怨sendClear不存在。尽管在base class中确实定义了sendClear。

问题在于,当编译器见到LoggingMsgSender这个模板类的定义时,并不知道它继承什么样的类。当然它继承的是MsgSender<Company>,但其中的Company是个template参数,只有在具现化LoggingMsgSender才知道Company是什么,而如果不知道Company是什么,就无法知道MsgSender<Company>是否有个sendClear函数。

比如,现在有个公司只能发送密文:

class CompanyZ {                             // this class offers no
public: // sendCleartext function
...
void sendEncrypted(const std::string& msg);
...
};

此时一般性的MsgSender对CompanyZ就不合适了,必须产生一个MsgSender特化版:

template<>                                 // a total specialization of
class MsgSender<CompanyZ> { // MsgSender; the same as the
public: // general template, except
... // sendCleartext is omitted
void sendSecret(const MsgInfo& info)
{ ... }
};

现在有个MsgSender针对CompanyZ的全特化版本,再次考虑LoggingMsgSender的实现,当base class指定为MsgSender<CompanyZ>时,这段代码不合法,因为该base class没有sendClear函数。

这就是编译失败的原因,编译器知道base class模板类可能被特化,而特化版本可能不提供一般性模板相同的接口,所以它才拒绝在模板化基类(本例中的MsgSender<Company>)内寻找继承而来的名称(本例中的SendClear)。

有三种办法可以明确指出使编译器进入模板基类中寻找名称:第一是在base class的函数调用时加上this->:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
//write "before sending" info to the log;
this->sendClear(info); // okay, assumes that sendClear will be inherited
//write "after sending" info to the log;
}
...
};

第二是使用using声明式:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
using MsgSender<Company>::sendClear; // tell compilers to assume
// that sendClear is in the base class
void sendClearMsg(const MsgInfo& info)
{
...
sendClear(info); // okay, assumes that sendClear will be inherited
...
}
};

第三是明确指出函数位于base class内:

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info)
{
...
MsgSender<Company>::sendClear(info); // okay, assumes that
... // sendClear will be inherited
}
};

第三种方法有个缺点,就是当被调用的virtual函数时,会关闭virtual绑定行为。

从名字可见性的观点来看,上面方法都做了同样的事情:它向编译器保证任何后继的 base class template(基类模板)的特化版本都将支持通用模板提供的接口。

但是如果保证被证实不成立,真相将在后继的编译过程中暴露。例如,如果后面的源代码中包含这些:

LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
... // put info in msgData
zMsgSender.sendClearMsg(msgData); // error! won't compile

对 sendClearMsg 的调用将不能编译,因为在此刻,编译器知道 base class是MsgSender<CompanyZ>,也知道那个类没有提供 sendClear函数。

从根本上说,问题就是编译器是早些(当 derived class template definitions(派生类模板定义)被解析的时候)诊断对 base class members(基类成员)的非法引用,还是晚些时候(当那些 templates(模板)被特定的 template arguments(模板参数)实例化的时候)再进行。C++ 的方针是宁愿早诊断,而这就是为什么当那些 classes(类)被从 templates(模板)实例化的时候,它假装不知道 base classes(基类)的内容。

44:将与参数无关的代码抽离template

为了避免重复代码,当编写某个普通函数,其中某些部分的实现码和另一个函数的实现码实质相同,此时,抽出两个函数的共同部分,把它们放进第三个函数中,然后令原先两个函数调用这个新函数。同样道理,如果你正在编写某个class,而其中某些部分和另一个class的某些部分相同,可以把共同部分搬移到新class去,然后使用继承或复合,令原先的classes取用这共同特性。而原classes的互异部分仍然留在原位置不动。

编写templates时,也可以做同样的优化,以相同的方式避免重复。在non-template代码中,重复十分明确;然而在template代码中,重复是隐晦的。

举个例子,为固定尺寸的正方矩阵编写一个支持逆矩阵运算的template:

template<typename T, std::size_t n>
class SquareMatrix {
public:
...
void invert();
}; SquareMatrix<double, > sm1;
sm1.invert(); // call SquareMatrix<double, 5>::invert SquareMatrix<double, > sm2;
sm2.invert(); // call SquareMatrix<double, 10>::invert

sm1.invert和sm2.invert函数调用会具现化两份invert。这些函数并非完完全全相同,但除了常量5和10,两个函数的其他部分完全相同。这是template引出代码膨胀的一个典型例子。

首先想到为它们建立一个带数值参数的函数,然后以5和10来调用这个带参数的函数,而不重复代码:

template<typename T>                   // size-independent base class for
class SquareMatrixBase { // square matrices
protected:
...
void invert(std::size_t matrixSize); // invert matrix of the given size
...
}; template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T> {
private:
using SquareMatrixBase<T>::invert;
public:
...
void invert() { this->invert(n); }
};

SquareMatrixBase也是个template,不同的是它只对“矩阵元素对象的类型”参数化,不对矩阵的尺寸参数化。因此对于某给定的元素对象类型,所有矩阵共享同一个SquareMatrixBase class。它们也将因此共享这唯一一个class内的invert函数。

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

智能指针的行为像指针,并提供真实指针没有的机制保证资源自动回收。真实指针支持隐式转换:Derived class指针可以隐式转换为base class指针,指向non-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<typename T>
class SmartPtr {
public: // smart pointers are typically
explicit SmartPtr(T *realPtr); // initialized by built-in pointers
...
}; SmartPtr<Top> pt1 = // convert SmartPtr<Middle> =>
SmartPtr<Middle>(new Middle); // SmartPtr<Top> SmartPtr<Top> pt2 = // convert SmartPtr<Bottom> =>
SmartPtr<Bottom>(new Bottom); // SmartPtr<Top> SmartPtr<const Top> pct2 = pt1; // convert SmartPtr<Top> =>
// SmartPtr<const Top>

上面的代码是错误的。同一个template的不同具现体之间并不存在什么先天的固有关系,也就是说:如果以带有base-derived关系的S, D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系。

所以,为了获得SmartPtr classes之间的转换能力,必须明确写出来。上面的语句时创建智能指针对象,所以考虑构造函数的实现。但是不可能写出所有需要的构造函数,上面的代码中,根据一个SmartPtr<Middle> 或 SmartPtr<Bottom> 构造出一个 SmartPtr<Top>,但是如果将来这个继承体系被扩充,还需要重新定义一个构造函数,这是不现实的。

我们需要的不是为SmartPtr写一个构造函数,而是写一个构造模板,这就是所谓的member function templates:

template<typename T>
class SmartPtr {
public:
template<typename U> // member template
SmartPtr(const SmartPtr<U>& other); // for a "generalized
... // copy constructor"
};

这个构造模板的意思是:对任何类型T和任何类型U,这里可以根据SmartPtr<U>生成一个SmartPtr<T>,有时又称之为泛化copy构造函数。

上面的声明还不够:我们希望根据一个SmartPtr<Bottom> 创建一个 SmartPtr<Top>,但是不需要能够从一个 SmartPtr<Top> 创建一个 SmartPtr<Bottom>,因此必须在某方面对这个member template所创建的成员函数群进行筛除。可以这样做:

template<typename T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr<U>& other) // initialize this held ptr
: heldPtr(other.get()) { ... } // with other's held ptr T* get() const { return heldPtr; }
...
private: // built-in pointer held
T *heldPtr; // by the SmartPtr
};

这里使用U*初始化T*,这个行为只有在:存在某个隐式转换可将U*指针转换为T*指针时才能通过编译。

成员函数模板的作用并不仅限于构造函数,它还可以作用于赋值操作副。

如果类没有定义copy构造函数,编译器会自动生成一个。在类内声明泛化copy构造函数并不会阻止编译器生成它们自己的copy构造函数。这个规则也适用于赋值操作。

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

条款24讨论过为什么只有非成员函数才能“在所有实参身上实施隐式类型转换”,该条款以Rational的operator*函数为例。现在将Rational和operator*模板化,代码如下:

template<typename T>
class Rational {
public:
Rational(const T& numerator = , // see Item 20 for why params
const T& denominator = ); // are now passed by reference const T numerator() const; // see Item 28 for why return
const T denominator() const; // values are still passed by value,
... // Item 3 for why they're const
}; template<typename T>
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs)
{ ... }

像条款24一样,我们希望支持混合式算术运算,所以我们希望下面的代码通过编译:

Rational<int> oneHalf(, );
Rational<int> result = oneHalf * ; // error! won't compile

上面的代码并不能通过编译。在条款24中,编译器知道我们尝试调用什么函数,但是这里编译器却不知道。虽然这里有个函数模板是operator*,它接受两个Rational<T>参数,但是在模板实参推导过程中,从不考虑隐式类型转换。

这里的调用中,operator*的参数一个是Rational<int>,另一个是int类型,没有这样的模板函数可以具现化出这样的函数,所以编译失败。

可以利用以下规则解决这个问题:模板类中的friend声明可以指涉某个特定函数:

template<typename T>
class Rational {
public:
...
friend
const Rational operator*(const Rational& lhs,
const Rational& rhs);
}; template<typename T> // define operator*
const Rational<T> operator*(const Rational<T>& lhs, // functions
const Rational<T>& rhs)
{ ... }

此时,当对象oneHalf被声明为一个Rational<int>时,类Rational<int>也就被具现化出来了,那么friend函数operator*也就自动被声明出来。后者作为一个函数而不是函数模板,编译器可以再调用它时使用隐式转换。

此时,代码虽然能够通过编译,但是却无法链接成功。当声明一个Rational<int>时,该类被具现化出来,但其中的friend声明也仅仅是个声明,还没有找到定义,也就是函数体并未具现化(尽管在Rational外部提供了该friend的定义,但是那是一个函数模板,在没有遇到参数匹配的函数调用之前,不会具现化,这里的调用时oneHalf*2,参数不匹配,所以不会具现化这个模板)。

最简单的解决办法就是讲定义体放在Rational内:

template<typename T>
class Rational {
public:
... friend const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(), // same impl
lhs.denominator() * rhs.denominator()); // as in
} // Item 24
};

现在可以通过编译、连接,并能执行了。

这个技术的趣味点是:虽然使用了friend,但是与friend的传统用途“访问class中非public部分”毫不相干:为了让类型转换可以作用于所有实参上,需要一个non-member函数(条款24);为了这个函数能自动具现化,需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是令他成为一个friend。

条款30中说,class内定义的函数都自动成为inline,包括像operator*这样的friend函数。可以这样将inline声明所带来的冲击最小化:让operator*不做任何事情,而是调用一个定义与class外部的辅助函数(本例中意义不大,因为operator*已经是个单行函数,但对复杂函数而言,这样做也许有意义)。

Rational是个模板,意味着那个辅助函数通常也是个模板,所以代码如下:

template<typename T> class Rational; 

template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,
const Rational<T>& rhs); template<typename T>
class Rational {
public:
...
friend
const Rational<T> operator*(const Rational<T>& lhs,
const Rational<T>& rhs) // Have friend
{ return doMultiply(lhs, rhs); } // call helper
...
}; template<typename T> // define
const Rational<T> doMultiply(const Rational<T>& lhs, // helper
const Rational<T>& rhs) // template in
{ // header file,
return Rational<T>(lhs.numerator() * rhs.numerator(), // if necessary
lhs.denominator() * rhs.denominator());
}

因为定义在Rational内部的operator*需要调用doMultiply函数模板,所以,需要在Rational之前声明doMultiply,而doMultiply原型中,又用到了Rational模板,所以在它之前又需要声明Rational。

作为一个template,doMultiply当然不支持混合式乘法,但它其实不需要。他只是被operator*调用,而operator*支持混合式乘法,也就是调用operator*时,参数已经完成了隐式转换。

47:使用traits classes表现类型信息

STL中有一个名为advance的template,它用于将某个迭代器移动指定距离:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d);

只有随机访问迭代器支持+=操作,所以其他类型的迭代器,在advance中只能反复执行++或--操作,供d次。

STL共有5中迭代器分类,对应于它们支持的操作:input迭代器只能向前移动,一次一步,只能读取它们所指的东西,而且只能读取一次,istream_iterators就是这种迭代器;output迭代器类似,只能向前移动,一次一步,只能写它们所指的东西,且只能写一次,ostream_iterators是这类迭代器;forward迭代器,可以做前述两种类型迭代器所能做的每件事,且可以读或写所指物一次以上,单向链表类型的容器的迭代器就属于forward迭代器;bidirectional迭代器除了可以向前移动,也可以向后移动。set,map等的迭代器属于这一类;最强大的迭代器是random access迭代器,它更强大的地方在于可以执行迭代器算术,也就是常量时间内向前或向后跳跃任意距离。vector、deque,string的迭代器属于这一类,指针也被当做random access迭代器。

对于这5中迭代器,C++标准库提供了卷标结构用以区分:

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函数,它的伪代码应该是下面这个样子:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter is a random access iterator) {
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= ) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}

这就需要判断iter是否为random access迭代器,也就是我们需要取得类型信息。这就是traits的作用,它允许你在编译期获得某些类型信息。traits是一种技术,也是一种约定,它的要求之一是需要对内置类型和用户自定义类型要表现的一样好。也就是说,advance收到的实参如果是一个指针,则advance也需要能够运作。

因为traits需要支持内置类型,所以traits信息必须位于类型自身之外。比如,在标准库中,针对迭代器的traits就命名为iterator_traits:

template<typename IterT> struct iterator_traits; 

习惯上,traits总是被实现为structs。iterator_traits的运作方式是,针对每一个类型IterT,在struct iterator_traits<IterT>内一定声明某个typedef名为iterator_category。这个typedef用来确认IterT的迭代器分类。

iterator_traits以两部分实现上述所言。首先它要求每一个用户自定义的迭代器类型必须嵌套一个typedef,名为iterator_category。比如deque和list的迭代器定义:

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这个模板类中,只是简单的鹦鹉学舌:

// the iterator_category for type IterT is whatever IterT says it is;
template<typename IterT>
struct iterator_traits {
typedef typename IterT::iterator_category iterator_category;
...
};

上面的做法对于指针是行不通的,所以有一个偏特化版本:

template<typename IterT>
struct iterator_traits<IterT*>
{
typedef random_access_iterator_tag iterator_category;
...
};

现在可以对advance实践先前的伪代码:

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))
...
}

实际上这个实现是有编译问题的,而且IterT类型在编译期间就可知了,所以iterator_traits<IterT>::iterator_category也可以在编译期间确定。但if语句却是在运行期才能确定。

我们真正需要的是在编译期间就能判断类型是否相同,在C++中,函数的重载就是在编译期间确定类型的例子。所以,可以使用重载技术实现advance:

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 >= ) { while (d--) ++iter; }
else { while (d++) --iter; }
} template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
{
if (d < ) {
throw std::out_of_range("Negative distance");
}
while (d--) ++iter;
}

forward_iterator_tag 继承自 input_iterator_tag,所以上面的doAdvance的input_iterator_tag版本也能处理forward迭代器。

实现了这些doAdvance重载版本之后,advance的代码如下:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance(
iter, d,
typename std::iterator_traits<IterT>::iterator_category()
);
}

TR1导入了许多新的traits classes用以提供类型信息,包括is_fundamental<T>判断T是否为内置类型,is_array<T>判断T是否是数组,以及is_base_of<T1, T2>判断T1和T2是否相同,或者是否T1是T2的base class。

48:认识template元编程

所谓template metaprogram(TMP,模板元程序)是以C++写成、执行于C++编译期内的程序。一旦TMP程序结束执行,其输出,也就是从templates具现出来的若干C++源码,便会一如往常地被编译。

TMP有两个效力。第一,它让某些事情更容易。如果没有它,那些事情将是困难的,甚至不可能的。第二,由于template metaprograms执行于C++编译期,因此可将工作从运行期转移到编译期。这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。然而将工作从运行期移转至编译期的另一个结果是,编译时间变长了。

条款47中提到的advance的traits实现,就是TMP的例子。在条款47中,还提到了一种运行期判断类型的实现:

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)) {
iter += d; // use iterator arithmetic
} // for random access iters
else {
if (d >= ) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}

这个实现不但效率低,而且还会存在编译问题,比如针对std::list<int>::iterator iter的具现化代码如下:

void advance(std::list<int>::iterator& iter, int d)
{
if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d; // error!
}
else {
if (d >= ) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}

问题出在iter+=d上,这条语句尝试在list<int>::iterator上使用其不支持的+=操作。尽管在运行期间永远不会执行+=操作,但是编译期间编译器必须确保所有源码有效,某条语句执行类型不支持某种操作肯定会引起编译错误。

TMP已被证明是个图灵完备的,也就是说TMP可以计算任何事物,使用TMP可以声明变量,执行循环,编写调用函数等。比如循环,TMP中没有真正的循环构件,循环效果是通过递归实现的。以计算阶乘为例:

template<unsigned n>
struct Factorial {
enum { value = n * Factorial<n->::value };
}; template<> // special case: the value of
struct Factorial<> { // Factorial<0> is 1
enum { value = };
};

可以这样使用Factorial:

int main()
{
std::cout << Factorial<>::value; // prints 120
std::cout << Factorial<>::value; // prints 3628800
}

以上只是一个TMP最简单的例子,TMP还有很多更酷的玩法。

TMP的缺点是语法不够直观,或许TMP不会成为主流,但是对某些程序员,特别是程序库开发人员,几乎肯定会成为他们的主要粮食。

Effective C++: 07模板与泛型编程的更多相关文章

  1. 【effective c++】模板与泛型编程

    模板元编程:在c++编译器内执行并于编译完成时停止执行 1.了解隐式接口和编译期多态 面向对象编程总是以显式接口(由函数名称.参数类型和返回类型构成)和运行期多态(虚函数)解决问题 模板及泛型编程:对 ...

  2. 《Effective C++》模板与泛型编程:条款32-条款40

    条款41:了解隐式接口和编译期多态 class支持显示接口和运行期多态 class的显示接口由函数的名签式构成(函数名称.参数类型.返回类型) class的多态通过virtual函数发生在运行期 te ...

  3. C++ 模板与泛型编程

    <C++ Primer 4th>读书笔记 所谓泛型编程就是以独立于任何特定类型的方式编写代码.泛型编程与面向对象编程一样,都依赖于某种形式的多态性. 面向对象编程中的多态性在运行时应用于存 ...

  4. C++ Primer 学习笔记_76_模板与泛型编程 --模板定义[续]

    模板与泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...

  5. C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

    模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一 ...

  6. C++ Primer 学习笔记_77_模板与泛型编程 --实例化

    模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实 ...

  7. C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

    模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还能够仅仅特化push和pop成员.我们将特化push成员以复制字符数组,而且特化pop成员以释放该副本使用的内存: ...

  8. C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

    模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...

  9. C++ Primer 学习笔记_76_模板和泛型编程 --模板定义[继续]

    模板和泛型编程 --模板定义[续] 四.模板类型形參 类型形參由keywordclass或 typename后接说明符构成.在模板形參表中,这两个keyword具有同样的含义,都指出后面所接的名字表示 ...

随机推荐

  1. nginx 访问ssl 的 pem 遇到的权限问题

    nginx 开启失败,日志记录 访问 ssl 的 pem 文件  fopen:Permission denied 权限问题,查看文件权限,目录权限,正常. google 后得知原来是一个 SELinu ...

  2. LRU Cache数据结构简介

    什么是LRU Cache LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法. 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM ...

  3. PAT甲级——A1019 General Palindromic Number

    A number that will be the same when it is written forwards or backwards is known as a Palindromic Nu ...

  4. Activiti实战03_Hello World

    Hello World如此经典,以至于几乎学习没一门新的技术都是从Hello World开始,可能意味着开启了新世界的大门吧,接下来就让我们一起步入到Activiti的世界中吧! 本文所使用开发环境 ...

  5. non-identifying and identifying

    An identifying relationship means that the child table cannot be uniquely identified without the par ...

  6. Luogu P1712 [NOI2016]区间(线段树)

    P1712 [NOI2016]区间 题意 题目描述 在数轴上有 \(N\) 个闭区间 \([l_1,r_1],[l_2,r_2],...,[l_n,r_n]\) .现在要从中选出 \(M\) 个区间, ...

  7. 从大数据到快数据 数据智创未来——2019 CCF大数据与计算智能大赛正式开赛!

    8月17日,以“数据驱动,智创未来”为主题的2019 CCF大数据与计算智能大赛(CCF Computing Intelligence Contest,简称CCF BDCI)全球启动仪式,在北京大学正 ...

  8. clientHeight、offsetHeight 区别 笔记

    一张图 说明全部 clientHeight和clientWidth用于描述元素内尺寸,是指元素内容+内边距大小,不包括边框(低版本IE下实际包括).外边距.滚动条部分 offsetHeight和off ...

  9. JavaScript 实例、构造函数、原型对象关系图

    详细介绍:深入理解javascript原型和闭包(5)——instanceof 图片来源:https://www.ibm.com/developerworks/cn/web/1306_jiangjj_ ...

  10. Codeforces Round #573 (Div. 2)

    A:Tokitsukaze and Enhancement 当时看错条件了..以为A>C>B>D.就胡写了判断条件. #include<bits/stdc++.h> us ...