870920 Menu

SwingCoder之C++备忘录·16

模板技术

函数模板的语法:
template <typename T>
const T& min(const T& t1, const T& t2) { return (t1 > t2) ? t2 : t1; }

类模板的语法:
template <typename T>
class tplClass
{
T& value;
public:
template <typename T>
void setValue (T& t) { value = t; }
};

 模板就是一种类型参数化的编程技巧。模板的参数可以是不确定的泛类型,也可以是确定的类型。这种情况下,类模板生成模板类时,确定类型的模板参数需给出该类型的类名。
 泛型编程的目的是写出类型无关、可重用、清晰高效的代码,C++中实现泛型编程主要依赖模板技术。
 泛型编程与面向对象编程都是编程的方法和基本理念,二者并无关联,但可以结合使用。
 C++的类模板与面向对象(继承多态)无关。但是由类模板生成的模板类支持各种面向对象技术。
 程序设计阶段优先考虑使用模板技术。
 模板和继承均体现了代码重用的思想,不同处:模板技术与基于运行时的多态机制无关,它是静态编译的,是更抽象、更灵活、适应性更好的编程理念。使用模板技术会稍微增加代码的编译时间,但运行效率则大大高于基于继承、虚函数和函数重载等常规手段的多态机制。
 模板侧重于对相同操作、相同算法的抽象和提取,而继承侧重于相同操作但算法与步骤不同。在可能的情况下,优先考虑模板,而后考虑聚合与组合,最后再考虑继承。尽量不要创建模板类的派生类,而是作为终端类。
 函数模板的形参类型和返回值类型均可参数化,即函数模板的模板参数可以是不确定的泛类型。
 模板参数的名字在整个模板的作用域内都可见。
 类模板并不是类,而是用于生成类的“类工厂”。具体生成什么类型的类,根据模板参数而定,在创建对象时,类模板后面的<类型名>确定了模板类的具体类型。同理,函数模板也并非函数,而是用于生成函数的“函数工厂”。所生成的具体函数,也是根据函数名后面的<类型名>来确定。
 类模板与模板类是两个概念。类模板用于生成模板类,而模板类同普通的类,可以创建对象。
 类模板中可有静态成员,但类模板生成的不同类型的模板类对象之间并不共享类模板中的静态成员。因为类模板可生成多种类型的具体类。只有相同类型的模板类的对象才共享类模板中的静态成员。
 模板类与普通类一样,可有基类,也可派生子类,其基类和子类既可以是模板类,也可以是普通类。所有与继承相关的特点,模板类均具备和符合。
 类模板的模板参数可有默认值。如果某个模板参数有默认值,则该类中所有的此模板参数都必须要有默认值。
 类模板中使用模板参数的成员函数不能是virtual虚函数。也不能编写带模板参数的虚析构函数。但是类模板的析构函数可以是虚函数(即:类模板的析构函数中不要使用模板参数)。
 如果由模板类声明对象的语句太笨拙,可使用typedef简化之,比如:
ScopedPointer<MyClass> ptr = new MyClass();
改为:
typedef ScopedPointer<MyClass> MyPtr; // 使用typedef简化模板类的声明语句
MyPtr ptr = new MyClass();
 现有的编译器均不支持export关键字(C++ 11已废除此关键字),无法做到类模板的头源分离,故类模板的类定义和类实现须在同一个文件中,一般写在h头文件中。实在看不下去,可以在头文件中只写类定义,将类实现写在非cpp为扩展名的文本文件中,而后在头文件的最后#include该文本文件。
 模板参数关键字可使用typename,也可使用class。通常,任何类型均可使用typename,而class专用于指明该模板参数为类类型。两者之间,优先使用typename。
 类模板的特例与实例是两个本质不同的概念。实例是该类模板所生成的模板参数(类型)具体化的类,特例则类模板的例外情况(即模板参数所不适用的具体类型),特例给出了具体的类型并重写了类模板的所有实现。类模板的特例和实例均可直接声明对象。
 模板特例:如果类模板的模板参数不适用于某个数据类型或类类型,则可以为该类型编写一个特例化的模板类。其语法有点奇怪,见下(比如Grid是一个类模板):
#include “Grid.h” // 该特例类隶属的类模板
template <> // 表明本类是一个类模板的特例
class Grid<char*> // 类声明为模板类的形式
{
public:
Grid(); // 重写类模板的构造函数
Grid(const Grid<char*>& src); // 编写本类的拷构函数与赋值运算符重载
Grid<char*>& operator= (const Grid<char*>& rhs);
private:
char** cells; // 改写类模板的数据成员。类模板的数据成员是:T** cells;
};
注意:特例化和子类继承不同,类模板的特例类必须重新编写类模板的所有实现。
 模板元编程是一个非常复杂的主题,其主要作用是:在编译时执行一些计算,而不是在运行时执行这些计算。通常需要使用模板递归技术。所谓模板递归:由于一个类模板,而后还有一个该类模板的实例化模板类,这两个(类模板及其模板类)协同配合,完成一些复杂的任务。