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. N!中素因子p的个数 【数论】

    求N!中素因子p的个数,也就是N!中p的幂次 公式为:cnt=[n/p]+[n/p^2]+[n/p^3]+...+[n/p^k]; 例如:N=12,p=2 12/2=6,表示1~12中有6个数是2的倍 ...

  2. Java爬虫的实现

    距离上一次写爬虫还是几年前了,那时候一直使用的是httpclient. 由于最近的项目又需要使用到爬虫,因此又重新查询了一些爬虫相关的框架,其中最合适的是WebMagic 官方文档:https://g ...

  3. 通过BlukLoad的方式快速导入海量数据

    http://www.cnblogs.com/MOBIN/p/5559575.html 摘要 加载数据到HBase的方式有多种,通过HBase API导入或命令行导入或使用第三方(如sqoop)来导入 ...

  4. LOJ 6042 跳蚤王国的宰相

    LOJ 6042 跳蚤王国的宰相 题意 跳蚤王国爆发了一场动乱,国王在镇压动乱的同时,需要在跳蚤国地方钦定一个人来做宰相. 由于当时形势的复杂性,很多跳蚤都并不想去做一个傀儡宰相,带着宰相的帽子,最后 ...

  5. Apache Flink 1.9重磅发布!首次合并阿里内部版本Blink重要功能

    8月22日,Apache Flink 1.9.0 版本正式发布,这也是阿里内部版本 Blink 合并入 Flink 后的首次版本发布.此次版本更新带来的重大功能包括批处理作业的批式恢复,以及 Tabl ...

  6. xhEditor 简单用法

    1.下载需要文件包: http://xheditor.com/ 2.解压文件中文件 xheditor-zh-cn.min.js以及xheditor_emot.xheditor_plugins和xhed ...

  7. iview中table的render()函数

    Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML.然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接 ...

  8. 破逼Json,该死的Json库,操了

    jansson,就这个库,破几把玩意,本来很简单的Json,就是简单的字符串操作,ATL一个CString就能解决,QT的QSting也能解决,DELPHI的String也能解决.而这B,非把那么简单 ...

  9. 设置Linux系统的空闲等待时间TMOUT的方法和Linux反空闲设置的两种方法

    为了增强linux系统的安全性,我们需要在用户输入空闲一段时间后自动断开,这个操作可以由设置TMOUT值来实现.将以下字段加入到/etc/profile 中即可(对所有用户生效). export TM ...

  10. OpenSmtp 发送邮件

    1.采用发送一个简单邮件 示例: private int smtpPort; private string smtpHost; private int recieveTimeout; private ...