16.1. Template Definitions16.1. 模板定义Let's imagine that we want to write a function to compare two values and indicate whether the first is less than, equal to, or greater than the second. In practice, we'd want to define several such functions, each of which could compare values of a given type. Our first attempt might be to define several overloaded functions: 假设想要编写一个函数比较两个值并指出第一个值是小于、等于还是大于第二个值。实践中,我们可能希望定义几个这样的函数,每一个可以比较一种给定类型的值,第一次尝试可能是定义几个重载函数: // returns 0 if the values are equal, -1 if v1 is smaller, 1 if v2 is smaller int compare(const string &v1, const string &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } int compare(const double &v1, const double &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } These functions are nearly identical: The only difference between them is the type of their parameters. The function body is the same in each function. 这些函数几乎相同,它们之间唯一的区别是形参的类型,每个函数的函数体是相同的。 Having to repeat the body of the function for each type that we compare is tedious and error-prone. More importantly, we need to know in advance all the types that we might ever want to compare. This strategy cannot work if we want to be able to use the function on types that we don't know about. 每个要比较的类型都需要重复函数的函数体,不仅麻烦而且容易出错。更重要的是,需要事先知道空间可能会比较哪些类型。如果希望将函数用于未知类型,这种策略就不起作用了。 16.1.1. Defining a Function Template16.1.1. 定义函数模板Rather than defining a new function for each type, we can define a single function template. A function template is a type-independent function that is used as a formula for generating a type-specific version of the function. For example, we might write a function template named compare, which would tell the compiler how to generate specific versions of compare for the types that we want to compare. 我们可以不用为每个类型定义一个新函数,而是只定义一个函数模板(function template)。函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。例如,可以编写名为 compare 的函数模板,它告诉编译器如何为我们想要比较的类型产生特定的 compare 版本。 The following is a template version of compare: 下面是 compare 的模板版本: // implement strcmp-like generic compare function // returns 0 if the values are equal, 1 if v1 is larger, -1 if v1 is smaller template <typename T> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } A template definition starts with the keyword template followed by a template parameter list, which is a comma-separated list of one or more template parameters bracketed by the less-than (<) and greater-than (>) tokens. 模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。
Template Parameter List模板形参表The template parameter list acts much like a function parameter list. A function parameter list defines local variable(s) of a specified type but leaves those variables uninitialized. At run time, arguments are supplied that initialize the parameters. 模板形参表很像函数形参表,函数形参表定义了特定类型的局部变量但并不初始化那些变量,在运行时再提供实参来初始化形参。 Analogously, template parameters represent types or values we can use in the definition of a class or function. For example, our compare function declares one type parameter named T. Inside compare, we can use the name T to refer to a type. Which actual type T represents is determined by the compiler based on how the function is used. 同样,模板形参表示可以在类或函数的定义中使用的类型或值。例如,compare 函数声明一个名为 T 的类型形参。在 compare 内部,可以使用名字 T 引用一个类型,T 表示哪个实际类型由编译器根据所用的函数而确定。 A template parameter can be a type parameter, which represents a type, or a nontype parameter, which represents a constant expression. A nontype parameter is declared following a type specifier. We'll see more about nontype parameters in Section 16.1.5 (p. 632). A type parameter is defined following the keyword class or typename. For example, class T is a type parameter named T. There is no difference between class and typename in this context. 模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。非类型形参跟在类型说明符之后声明,第 16.1.5 节将进一步介绍非类型形参。类型形参跟在关键字 class 或 typename 之后定义,例如,class T 是名为 T 的类型形参,在这里 class 和 typename 没有区别。 Using a Function Template使用函数模板When we use a function template, the compiler infers what template argument(s) to bind to the template parameter(s). Once the compiler determines the actual template argument(s), it instantiates an instance of the function template for us. Essentially, the compiler figures out what type to use in place of each type parameter and what value to use in place of each nontype parameter. Having deduced the actual template arguments, it generates and compiles a version of the function using those arguments in place of the corresponding template parameters. The compiler takes on the tedium of (re)writing the function for each type we use. 使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。实质上,编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参。推导出实际模板实参后,编译器使用实参代替相应的模板形参产生编译该版本的函数。编译器承担了为我们使用的每种类型而编写函数的单调工作。 Given the calls 对于以下调用 int main () { // T is int; // compiler instantiates int compare(const int&, const int&) cout << compare(1, 0) << endl; // T is string; // compiler instantiates int compare(const string&, const string&) string s1 = "hi", s2 = "world"; cout << compare(s1, s2) << endl; return 0; } the compiler will instantiate two different versions of compare. The compiler will create one version that replaces T by int and a second version that uses string in place of T. 编译器将实例化 compare 的两个不同版本,编译器将用 int 代替 T 创建第一个版本,并用 string 代替 T 创建第二个版本。 inline Function Templatesinline 函数模板A function template can be declared inline in the same way as a nontemplate function. The specifier is placed following the template parameter list and before the return type. It is not placed in front of the template keyword. 函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前。 // ok: inline specifier follows template parameter list template <typename T> inline T min(const T&, const T&); // error: incorrect placement of inline specifier inline template <typename T> T min(const T&, const T&);
16.1.2. Defining a Class Template16.1.2. 定义类模板Just as we can define function templates, we can also define class templates. 就像可以定义函数模板一样,也可以定义类模板。
Our Queue must be able to hold objects of different types, so we'll define it as a class template. The operations our Queue will support are a subset of the interface of the standard queue: 我们自定义的 Queue 类必须能够支持不同类型的对象,所以将它定义为类模板。Queue 类将支持的操作是标准 queue 类接口的子集:
We'll look at how we might implement our Queue in Section 16.4 (p. 647), but we can start by defining its interface: 第 16.4 节将介绍怎样实现 Queue 类,这里先定义它的接口: template <class Type> class Queue { public: Queue (); // default constructor Type &front (); // return element from head of Queue const Type &front () const; void push (const Type &); // add element to back of Queue void pop(); // remove element from head of Queue bool empty() const; // true if no elements in the Queue private: // ... }; A class template is a template, so it must begin with the keyword template followed by a template parameter list. Our Queue template takes a single template type parameter named Type. 类模板也是模板,因此必须以关键字 template 开头,后接模板形参表。Queue 模板接受一个名为 Type 的模板类型形参。 With the exception of the template parameter list, the definition of a class template looks like any other class. A class template may define data, function, and type members; it may use access labels to control access to those members; it defines constructors and destructors; and so on. In the definition of the class and its members, we can use the template parameters as stand-ins for types or values that will be supplied when the class is used. 除了模板形参表外,类模板的定义看起来与任意其他类问相似。类模板可以定义数据成员、函数成员和类型成员,也可以使用访问标号控制对成员的访问,还可以定义构造函数和析构函数等等。在类和类成员的定义中,可以使用模板形参作为类型或值的占位符,在使用类时再提供那些类型或值。 For example, our Queue template has one template type parameter. We can use that parameter anywhere a type name can be used. In this template definition, we use Type to name the return type from the overloaded front operations and as the parameter type for the push operation. 例如,Queue 模板有一个模板类型形参,可以在任何可以使用类型名字的地方使用该形参。在这个模板定义中,用 Type 指定重载 front 操作的返回类型以及作为 push 操作的形参类型。 Using a Class Template使用类模板In contrast to calling a function template, when we use a class template, we must explicitly specify arguments for the template parameters: 与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参: Queue<int> qi; // Queue that holds ints Queue< vector<double> > qc; // Queue that holds vectors of doubles Queue<string> qs; // Queue that holds strings The compiler uses the arguments to instantiate a type-specific version of the class. Essentially, the compiler rewrites our Queue class replacing Type by the specified actual type provided by the user. In this case, the compiler will instantiate three classes: a version of Queue with Type replaced by int, a second Queue class that uses vector<double> in place of Type, and a third that replaces Type by string. 编译器使用实参来实例化这个类的特定类型版本。实质上,编译器用用户提供的实际特定类型代替 Type,重新编写 Queue 类。在这个例子中,编译器将实例化三个 Queue 类:第一个用 int 代替 Type,第二个用 vector<double> 代替 Type,第三个用 string 代替 Type。 16.1.3. Template Parameters16.1.3. 模板形参As with a function parameter, the name chosen by the programmer for a template parameter has no intrinsic meaning. In our example, we named compare's template type parameter T, but we could have named it anything: 像函数形参一样,程序员为模板形参选择的名字没有本质含义。在我们的例子中,将 compare 的模板类型形参命名为 T,但也可以将它命名为任意名字:
// equivalent template definition
template <class Glorp>
int compare(const Glorp &v1, const Glorp &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
This code defines the same compare template as before. 该代码定义的 compare 模板与前面一样。 The only meaning we can ascribe to a template parameter is to distinguish whether the parameter is a type parameter or a nontype parameter. If it is a type parameter, then we know that the parameter represents an as yet unknown type. If it is a nontype parameter, we know it is an as yet unknown value. 可以给模板形参赋予的唯一含义是区别形参是类型形参还是非类型形参。如果是类型形参,我们就知道该形参表示未知类型,如果是非类型形参,我们就知道它是一个未知值。 When we wish to use the type or value that a template parameter represents, we use the same name as the corresponding template parameter. For example, all references to Glorp in the compare function template will be resolved to the same type when the function is instantiated. 如果希望使用模板形参所表示的类型或值,可以使用与对应模板形参相同的名字。例如,compare 函数中所有的 Glorp 引用将在该函数被实例化时确定为同一类型。 Template Parameter Scope模板形参作用域The name of a template parameter can be used after it has been declared as a template parameter and until the end of the template declaration or definition. 模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。 Template parameters follow normal name-hiding rules. A template parameter with the same name as an object, function, or type declared in global scope hides the global name: 模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字: typedef double T; template <class T> T calc(const T &a, const T &b) { // tmp has the type of the template parameter T // not that of the global typedef T tmp = a; // ... return tmp; } The global typedef that defines T as double is hidden by the type parameter named T. Thus, tmp is not a double. Instead, the type of tmp is whatever type gets bound to the template parameter T. 将 T 定义为 double 的全局类型型别名将被名为 T 的类型形参所屏蔽,因此,tmp 不是 double 型,相反,tmp 的类型是绑定到模板形参的任意类型。 Restrictions on the Use of a Template Parameter Name使用模板形参名字的限制A name used as a template parameter may not be reused within the template: 用作模板形参的名字不能在模板内部重用。
template <class T> T calc(const T &a, const T &b)
{
typedef double T; // error: redeclares template parameter T
T tmp = a;
// ...
return tmp;
}
This restriction also means that the name of a template parameter can be used only once within the same template parameter list: 这一限制还意味着模板形参的名字只能在同一模板形参表中使用一次: // error: illegal reuse of template parameter name V template <class V, class V> V calc(const V&, const V&) ; Of course, just as we can reuse function parameter names, the name of a template parameter can be reused across different templates: 当然,正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:
// ok: reuses parameter type name across different templates
template <class T> T calc (const T&, const T&) ;
template <class T> int compare(const T&, const T&) ;
Template Declarations模板声明As with any other function or class, we can declare a template without defining it. A declaration must indicate that the function or class is a template: 像其他任意函数或类一样,对于模板可以只声明而不定义。声明必须指出函数或类是一个模板: // declares compare but does not define it template <class T> int compare(const T&, const T&) ; The names of the template parameters need not be the same across declarations and the definition of the same template: 同一模板的声明和定义中,模板形参的名字不必相同。 // all three uses of calc refer to the same function template // forward declarations of the template template <class T> T calc(const T&, const T&) ; template <class U> U calc(const U&, const U&) ; // actual definition of the template template <class Type> Type calc(const Type& a, const Type& b) { /* ... */ } Each template type parameter must be preceded either by the keyword class or typename; each nontype parameter must be preceded by a type name. It is an error to omit the keyword or a type specifier: 每个模板类型形参前面必须带上关键字 class 或 typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的: // error: must precede U by either typename or class template <typename T, U> T calc (const T&, const U&) ;
16.1.4. Template Type Parameters16.1.4. 模板类型形参Type parameters consist of the keyword class or the keyword typename followed by an identifier. In a template parameter list, these keywords have the same meaning: They indicate that the name that follows represents a type. 类型形参由关键字 class 或 typename 后接说明符构成。在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。 A template type parameter can be used as a type specifier anywhere in the template, in exactly the same way as a built-in or class type specifier. In particular, it can be used to name the return type or a function parameter type, and for variable declarations or casts inside the function body: 模板类型形参可作为类型说明符在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同。具体而言,它可以用于指定返回类型或函数形参类型,以及在函数体中用于变量声明或强制类型转换。 // ok: same type used for the return type and both parameters template <class T> T calc (const T& a, const T& b) { // ok: tmp will have same type as the parameters & return type T tmp = a; // ... return tmp; } Distinction Between typename and classtypename 与 class 的区别In a function template parameter list, the keywords typename and class have the same meaning and can be used interchangeably. Both keywords can be used in the same template parameter list: 在函数模板形参表中,关键字 typename 和 class 具有相同含义,可以互换使用,两个关键字都可以在同一模板形参表中使用:
// ok: no distinction between typename and class in template parameter list
template <typename T, class U> calc (const T&, const U&);
It may seem more intuitive to use the keyword typename instead of the keyword class to designate a template type parameter; after all, we can use built-in (nonclass types) types as the actual type parameter. Moreover, typename more clearly indicates that the name that follows is a type name. However, the keyword typename was added to C++ as part of Standard C++, so older programs are more likely to use the keyword class exclusively. 使用关键字 typename 代替关键字 class 指定模板类型形参也许更为直观,毕竟,可以使用内置类型(非类类型)作为实际的类型形参,而且,typename 更清楚地指明后面的名字是一个类型名。但是,关键字 typename 是作为标准 C++ 的组成部分加入到 C++ 中的,因此旧的程序更有可能只用关键字 class。 Designating Types inside the Template Definition在模板定义内部指定类型In addition to defining data or function members, a class may define type members. For example, the library container classes define various types, such as size_type, that allow us to use the containers in a machine-independent way. When we want to use such types inside a function template, we must tell the compiler that the name we are using refers to a type. We must be explicit because the compiler (and a reader of our program) cannot tell by inspection when a name defined by a type parameter is a type or a value. As an example, consider the following function: 除了定义数据成员或函数成员之外,类还可以定义类型成员。例如,标准库的容器类定义了不同的类型,如 size_type,使我们能够以独立于机器的方式使用容器。如果要在函数模板内部使用这样的类型,必须告诉编译器我们正在使用的名字指的是一个类型。必须显式地这样做,因为编译器(以及程序的读者)不能通过检查得知,由类型形参定义的名字何时是一个类型何时是一个值。例如,考虑下面的函数: template <class Parm, class U> Parm fcn(Parm* array, U value) { Parm::size_type * p; // If Parm::size_type is a type, then a declaration // If Parm::size_type is an object, then multiplication } We know that size_type must be a member of the type bound to Parm, but we do not know whether size_type is the name of a type or a data member. By default, the compiler assumes that such names name data members, not types. 我们知道 size_type 必定是绑定到 Parm 的那个类型的成员,但我们不知道 size_type 是一个类型成员的名字还是一个数据成员的名字,默认情况下,编译器假定这样的名字指定数据成员,而不是类型。 If we want the compiler to treat size_type as a type, then we must explicitly tell the compiler to do so: 如果希望编译器将 size_type 当作类型,则必须显式告诉编译器这样做: template <class Parm, class U> Parm fcn(Parm* array, U value) { typename Parm::size_type * p; // ok: declares p to be a pointer } We tell the compiler to treat a member as a type by prefixing uses of the member name with the keyword typename. By writing typename Parm::size_type we say that member size_type of the type bound to Parm is the name of a type. Of course, this declaration puts an obligation on the types used to instantiate fcn: Those types must have a member named size_type that is a type. 通过在成员名前加上关键字 typename 作为前缀,可以告诉编译器将成员当作类型。通过编写 typename parm::size_type,指出绑定到 Parm 的类型的 size_type 成员是类型的名字。当然,这一声明给用实例化 fcn 的类型增加了一个职责:那些类型必须具有名为 size_type 的成员,而且该成员是一个类型。
16.1.5. Nontype Template Parameters16.1.5. 非类型模板形参A template parameter need not be a type. In this section we'll look at nontype parameters as used by function templates. We'll look at nontype parameters for class templates in Section 16.4.2 (p. 655) after we've seen more about how class templates are implemented. 模板形参不必都是类型。本节将介绍函数模板使用的非类型形参。在介绍了类模板实现的更多内容之后,第 16.4.2 节将介绍类模板的非类型形参。 Nontype parameters are replaced by values when the function is called. The type of that value is specified in the template parameter list. For example, the following function template declares array_init as a function template with one type and one nontype template parameter. The function itself takes a single parameter, which is a reference to an array (Section 7.2.4, p. 240): 在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。例如,下面的函数模板声明了 array_init 是一个含有一个类型模板形参和一个非类型模板形参的函数模板。函数本身接受一个形参,该形参是数组的引用(第 7.2.4 节):
// initialize elements of an array to zero
template <class T, size_t N> void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
A template nontype parameter is a constant value inside the template definition. A nontype parameter can be used when constant expressions are requiredfor example, as we do hereto specify the size of an array. 模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参(例如,像这里所做的一样)指定数组的长度。 When array_init is called, the compiler figures out the value of the nontype parameter from the array argument: 当调用 array_init 时,编译器从数组实参计算非类型形参的值: int x[42]; double y[10]; array_init(x); // instantiates array_init(int(&)[42] array_init(y); // instantiates array_init(double(&)[10] The compiler will instantiate a separate version of array_init for each kind of array used in a call to array_init. For the program above, the compiler instantiates two versions of array_init: The first instance has its parameter bound to int[42], and in the other, that parameter is bound to double[10]. 编译器将为 array_init 调用中用到的每种数组实例化一个 array_init 版本。对于上面的程序,编译器将实例化 array_init 的两个版本:第一个实例的形参绑定到 int[42],另一个实例中的形参绑定到 double[10]。 Type Equivalence and Nontype Parameters类型等价性与非类型形参Expressions that evaluate to the same value are considered equivalent template arguments for a template nontype parameter. The following calls to array_init both refer to the same instantiation, array_init<int, 42>: 对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。下面的两个 array_init 调用引用的是相同的实例—— array_init<int, 42>: int x[42]; const int sz = 40; int y[sz + 2]; array_init(x); // instantiates array_init(int(&)[42]) array_init(y); // equivalent instantiation
16.1.6. Writing Generic Programs16.1.6. 编写泛型程序When we write a template, the code may not be overtly type-specific, but template code always makes some assumptions about the types that will be used. For example, although our compare function is technically valid for any type, in practice the instantiated version might be illegal. 编写模板时,代码不可能针对特定类型,但模板代码总是要对将使用的类型做一些假设。例如,虽然 compare 函数从技术上说任意类型都是有效的,但实际上,实例化的版本可能是非法的。 Whether the generated program is legal depends on the operations used in the function and the operations supported by the type or types used. Our compare function has has three statements: 产生的程序是否合法,取决于函数中使用的操作以及所用类型支持的操作。compare 函数有三条语句: if (v1 < v2) return -1; // < on two objects of type T if (v2 < v1) return 1; // < on two objects of type T return 0; // return int; not dependent on T The first two statements contain code that implicitly depends on the parameter type. The if tests use the < operator on the parameters. The type of those parameters isn't known until the compiler sees a call to compare and T is bound to an actual type. Which < operator is used depends entirely on the argument type. 前两条语句包含隐式依赖于形参类型的代码,if 测试对形参使用 < 操作符,直到编译器看见 compare 调用并且 T 绑定到一个实际类型时,才知道形参的类型,使用哪个 < 操作符完全取决于实参类型。 If we call compare on an object that does not support the < operator, then the call will be invalid: 如果用不支持 < 操作符的对象调用 compare,则该调用将是无效的:
Sales_item item1, item2;
// error: no < on Sales_item
cout << compare(item1, item2) << endl;
The program is in error. The Sales_item type does not define the < operator, so the program won't compile. 程序会出错。Sales_item 类型没有定义 < 操作符,所以该程序不能编译。
Writing Type-Independent Code编写独立于类型的代码The art of writing good generic code is beyond the scope of this language primer. However, there is one overall guideline that is worth noting. 编写良好泛型代码的技巧超出了本书的范围,但是,有个一般原则值得注意。
Simple though it is, our compare function illustrates two important principles for writing generic code: 虽然简单,但它说明了编写泛型代码的两个重要原则:
By making the parameters const references, we allow types that do not allow copying. Most typesincluding the built-in types and, except for the IO types, all the library types we've useddo allow copying. However, there can be class types that do not allow copying. By making our parameters const references, we ensure that such types can be used with our compare function. Moreover, if compare is called with large objects, then this design will also make the function run faster. 通过将形参设为 const 引用,就可以允许使用不允许复制的类型。大多数类型(包括内置类型和我们已使用过的除 IO 类型之外的所有标准库的类型)都允许复制。但是,也有不允许复制的类类型。将形参设为 const 引用,保证这种类型可以用于 compare 函数,而且,如果有比较大的对象调用 compare,则这个设计还可以使函数运行得更快。 Some readers might think it would be more natural for the comparisons to be done using both the < and > operators: 一些读者可能认为使用 < 和 > 操作符两者进行比较会更加自然:
// expected comparison
if (v1 < v2) return -1;
if (v1 > v2) return 1;
return 0;
However, by writing the code as 但是,将代码编写为 // expected comparison if (v1 < v2) return -1; if (v2 < v1) return 1; // equivalent to v1 > v2 return 0; we reduce the requirements on types that can be used with our compare function. Those types must support <, but they need not also support >. 可以减少对可用于 compare 函数的类型的要求,这些类型必须支持 <,但不必支持 >。
![]() |