如何理解C++模板
2014-02-25苏子伟
苏子伟
首先谈一下什么是模板。模板就是为解决某一类问题,从而抽象了这类问题的具体共性,为这类问题提供一种通用的解决方法。工作中有很多这样的例子,比如我们熟知的Microsoft Office中的模板,它给我们的工作带来了很多便利,改变了每件事都得从头编码的做事方法,使得代码重用变得如此简单。
C++模板的基础讨论
C++模板的思想也是基于此,它抽象了具体的型别,实现了共性逻辑,为一类问题提供了统一的泛型接口。模板机制使得编程者在定义类和函数时能以类型作为参数,并且模板只依赖于实际使用时的传入参数,不关心能被用作参数的那些不同类型之间的任何联系。刚刚接触C++模板时,可能会感觉它不就是C的#define宏吗,只是在这里换了一种说法,无非是“新瓶装旧酒”啊。但是如果你对它感兴趣,再深入地理解一下,相信它会带给你很大的惊喜的。当前,C++模板已经变成了通用编程的基础,是标准模板库(STL)的基石。它使得用户接口更简单,表达更清晰。
C++是一门静态的语言,它是强类型检查的,所以代码不可能在运行时再编译生成。而C++模板的技术核心也在于编译期的代码解释和执行期的零成本。虽然这点同#define宏比较类似,但是它实现更简单,更不容易产生错误,使得代码看起来更具美感,并且它提供函数返回值,可以进行代码调试,可以进行编译期的变量类型检查等等,这些都不是#define宏能比拟的。下面让我们先看一下C++中如何定义一个模板。
怎么去定义一个模板
C++中有两种模板,一种是类模板,一种是函数模板,函数模板也可以在类模板中使用。因为模板是编译期生成代码的,所以一般我们都把模板代码放到一个头文件中声明定义。首先,我们看一下类模板。
template
{
enum { ssize = 50 };
T m_Stack[ssize];
intm_Top_postion;
public:
ClassName () :m_Top_postion(0) { }
voidpush_into(const T& i){
m_Stack[m_Top_postion++] = i;
}
T pop_up() {
returnm_Stack[--m_Top_postion];
}
int size(){
returnm_Top_postion;
}
};
template这个关键字告知编译器,随后的类定义ClassName将需要一个或更多的类型。这里的T是需要替换的类型参数,你可以指定T为任何你所需要的有意义的类型,比如整型、结构体类型,或者是一个模板类型等。(关于类模板中的常量以及详细的模板的语法,请参照C++语言程序设计者BjarneStroustrup的大作《C++程序设计语言》)
其次,是函数模板。它用于创建基于泛型为参数的函数。
template
这里的template和class的意义同类模板的一样。但是在函数模板中模板参数必须出现在函数参数中。
模板的实例化
C++模板提供了对源代码重用的方法,而不像是继承跟组合提供的对目标代码的重用方式。这使得处理问题的耦合更小,更简单,接口更加丰富,代码量更少。编译器会根据具体的参数,生成具体问题的解法的特定代码,这就是模板的实例化。下面让我们通过一个例子来认识一下模板代码是怎么生成具体的特定代码的。
template
T m_data[size];
intm_pos;
public:
ClassName () :m_pos(0) { }
voidput_into(const T& t)
{
m_data[m_pos++] = t;
}
};
下面我们定义一个变量ClassName
classClassName {
intm_data[60];
intm_pos;
public:
ClassName () :m_pos(0) { }
voidput_into(const T& t)
{
m_data[m_pos++] = t;
}
};
这就是模板参数的实例化过程,根据不同的参数生成了特定的类。函数模板的原理同类模板相同,这里就不在赘述了。
模板的特化
一个模板描述了某个范围内的一族函数或类。当给定模板参数时,这些模板参数决定了这一族函数或类中的独一无二的特例,这样的过程结果被称为特化。当然模板实例化也是特化的过程。特化分为全特化和半特化,但是函数模板不能半特化,必须一次性的全特化。下面我们分别看看这两中情况。
首先看看全特化,全特化使用了template<>来标识。
template
return (a > b) ? a : b;
}
// An explicit specialization of the max template
template<>
const char* const& compare
return (strlen(a) >strlen(b)) ? a : b;
}
这样我们使用模板时,例如compare<>(s1,s2)就是使用全特化后的模板生成的最终代码。compare(4,5)就是使用未全特化的模板生成的最终代码。
再看一个例子:
template
template<> class CAssert
在这个例子中实现的部分跟省略的一样多,但是它可以工作得很好。它的全特化只是实现了bool型别为true的情况,这样我们就能使用它在代码中进行静态断言。
再来看一下半特化,半特化又叫偏特化。这种特化方式只是针对于类模板。故名思议,半特化只是部分地特化了模板,约束了符合用户期望的行为的模板参数,其他参数仍然维持其泛型的性质。编译器在生成模板代码时会查找出最匹配的定义,来进行实例化。模板的半特化有很多巧妙的应用,它很好地处理了一些分支问题。下面就以loki库中的一个例子来看看这种迷人的用法。
比如你需要向一个容器里面插入变量,有时是T,有时是T*。Loki库的实现如下。
template
struct Select
{
Typedef T result;
};
template
struct Select
{
TypedefU result;
};
Select
模板的递归模式
这种模板的递归方式能使得每个派生类都派生于一个唯一的基类,这个基类使用了它本身作为模板参数。从理论上来说这会产生无休止的递归循环,使得编译器无法推断出派生类及其基类的具体的型别大小等,但是只要基类中的数据成员不含有与模板型别相关的依赖,模板是可以被实例化出来的。当程序设计者需要派生类具有某些性质,但是这些性质的实现通过继承又不能很好的处理时,此时可以考虑模板的递归模式。下面我们看一下具体的例子。
template
staticsize_t count;
public:
Counted() { ++count; }
Counted(const Counted
~Counted() { --count; }
staticsize_tgetCount() { return count; }
};
template
// children class definitions
classClass1 : public Counted
classClass2 : public Counted
这样我们的子类都继承了唯一的基类,并且基类的型别参数就是其自身。
模板的特征和策略
特征(traits)提供了类或类模板的类型接口,而策略(policy)提供了类或类模板的函数接口。特征和策略被大量地应用到了STL标准模板库,通过对策略的组合应用,可以使得同一个模板类产生出无与伦比的构建能力,从而涵盖更多的信息,接口更通用。
下面看一个具体的例子。
#include
#include
class cat {
public:
friendstd::ostream& operator<<(std::ostream&os, const cat&) {
returnos<< "cat!";
}
};
class dog {
public:
friendstd::ostream& operator<<(std::ostream&os, const dog&) {
returnos<< "dog!";
}
};
class Johnson {
public:
friendstd::ostream& operator<<(std::ostream&os, const Johnson&) {
returnos<< "Johnson";
}
};
class Tom {
public:
friendstd::ostream& operator<<(std::ostream&os, const Tom&) {
returnos<< "Tom";
}
};
template
template<> class PersonTraits
public:
typedef cat Favorite_thing;
};
template<> class PersonTraits
public:
typedef dog Favorite_thing;
};
class dance {
public:
std::stringtodo() { return std::string(" loves to dance."); }
};
class sing {
public:
std::stringtodo() { return std::string(" loves to sing."); }
};
template
class family {
person who;
typedeftypename traits::Favorite_thingFavorite_thing;
Favorite_thingfav;
actlike_to_do;
public:
family(const person& p) : who(p) {}
voidfavorite_thing() {
std::cout<< who <<" loves " < } }; int main() { Johnson John; family fam1.favorite_thing(); Tom tom; family fam2.favorite_thing(); } 这里面特征就是PersonTraits类里面的Favorite_thing,而策略就是todo()函数。这里我们能够看到,当把策略组合使用时,就是它们最有用的时候了。程序接口的使用者可以借由组合不同的策略来实现自己需要的高阶行为。建立策略类最重要的部分就是如何正确地分解策略。一般来说建立好的策略类,必须遵守的前提就是这些策略类必须是正交的。这样这些策略类彼此之间是不会产生耦合的。在我们设计类的时候,我们也应该考虑,某个机能如果有一个以上的解决方法,就应该考虑把该机能移出来,做成一个策略。这样我们就不会把大量的精力花在维护庞大的代码上了。 由于篇幅有限,本文只是讨论了C++模板的部分知识,还有很多有趣的应用等待我们去发掘,C++模板的开发与应用已经进入了一个全新之境。