16.6. Template Specializations16.6. 模板特化
It is not always possible to write a single template that is best suited for every possible template argument with which the template might be instantiated. In some cases, the general template definition is simply wrong for a type. The general definition might not compile or might do the wrong thing. At other times, we may be able to take advantage of some specific knowledge about a type to write a more efficient function than the one that is instantiated from the template. 我们并不总是能够写出对所有可能被实例化的类型都最合适的模板。某些情况下,通用模板定义对于某个类型可能是完全错误的,通用模板定义也许不能编译或者做错误的事情;另外一些情况下,可以利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数。 Our compare function and our Queue class are both good examples of the problem: Neither works correctly when used with C-style character strings. Let's look again at our compare function template: compare 函数和 Queue 类都是这一问题的好例子:与 C 风格字符串一起使用进,它们都不能正确工作。让我们再来看看 compare 函数模板: template <typename T> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } If we call this template definition on two const char* arguments, the function compares the pointer values. It will tell us the relative positions in memory of these two pointers but says nothing about the contents of the arrays to which the pointers point. 如果用两个 const char* 实参调用这个模板定义,函数将比较指针值。它将告诉我们这两个指针在内存中的相对位置,但没有说明归纳法指针所指数组的内容有关的任何事情。 To get be able to use compare with character strings, we would have to provide a specialized definition that knows how to compare C-style strings. The fact that these versions are specialized is transparent to users of these templates. Calls to a specialized function or use of a specialized class are indistinguishable from uses of a version instantiated from the general template. 为了能够将 compare 函数用于字符串,必须提供一个知道怎样比较 C 风格字符串的特殊定义。这些版本是特化的,这一事实对模板的用户透明。对用户而言,调用特化函数或使用特化类,与使用从通用模板实例化的版本无法区别。 16.6.1. Specializing a Function Template16.6.1. 函数模板的特化A template spacialization is a separate definition in which the actual type(s) or value(s) of one or more template parameter(s) is (are) specified. The form of a specialization is: 模板特化(template specialization)是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:
The following program defines a specialization of compare when the template parameter type is bound to const char*: 下面的程序定义了当模板形参类型绑定到 const char* 时,compare 函数的特化:
// special version of compare to handle C-style character strings
template <>
int compare<const char*>(const char* const &v1,
const char* const &v2)
{
return strcmp(v1, v2);
}
The declaration for the specialization must match that of the corresponding template. In this case, the template has one type parameter and two function parameters. The function parameters are const references to the type parameter. Here we are fixing the type parameter to const char*; our function parameters, therefore, are const references to a const char*. 特化的声明必须与对应的模板相匹配。在这个例子中,模板有一个类型形参和两个函数形参,函数形参是类型形参的 const 引用,在这里,将类型形参固定为 const char*,因此,函数形参是 const char* 的 const 引用。 Now when we call compare, passing it two character pointers, the compiler will call our specialized version. It will call the generic version for any other argument types (including plain char*): 现在,当调用 compare 函数的时候,传给它两个字符指针,编译器将调用特化版本。编译器将为任意其他实参类型(包括普通 char*)调用泛型版本: const char *cp1 = "world", *cp2 = "hi"; int i1, i2; compare(cp1, cp2); // calls the specialization compare(i1, i2); // calls the generic version instantiated with int Declaring a Template Specialization声明模板特化As with any function, we can declare a function template specialization without defining it. A template specialization declaration looks like the definition but omits the function body: 与任意函数一样,函数模板特化可以声明而无须定义。模板特化声明看起来与定义很像,但省略了函数体:
// declaration of function template explicit specialization
template<>
int compare<const char*>(const char* const&,
const char* const&);
This declaration consists of an empty template parameter list (template<>) followed by the return type, the function name (optionally) followed by explicit template argument(s) specified inside a pair of angle brackets, and the function parameter list. A template specialization must always include the empty template parameter specifier, template<>, and it must include the function parameter list. If the template arguments can be inferred from the function parameter list, there is no need to explicitly specify the template arguments: 这个声明由一个后接返回类型的空模板形参表(template<>),后接一对尖括号中指定的显式模板实参的函数名(可选),以及函数形参表构成。模板特化必须总是包含空模板形参说明符,即 template<>,而且,还必须包含函数形参表。如果可以从函数形参表推断模板实参,则不必显式指定模板实参: // error: invalid specialization declarations // missing template<> int compare<const char*>(const char* const&, const char* const&); // error: function parameter list missing template<> int compare<const char*>; // ok: explicit template argument const char* deduced from parameter types template<> int compare(const char* const&, const char* const&); Function Overloading versus Template Specializations函数重载与模板特化Omitting the empty template parameter list, template<>, on a specialization may have surprising effects. If the specialization syntax is missing, then the effect is to declare an overloaded nontemplate version of the function: 在特化中省略空的模板形参表 template<> 会有令人惊讶的结果。如果缺少该特化语法,则结果是声明该函数的重载非模板版本: // generic template definition template <class T> int compare(const T& t1, const T& t2) { /* ... */ } // OK: ordinary function declaration int compare(const char* const&, const char* const&); The definition of compare does not define a template specialization. Instead, it declares an ordinary function with a return type and a parameter list that could match those of a template instantiation. compare 的定义没有定义模板特化,相反,它声明了一个普通函数,该函数含有返回类型和可与模板实例化相匹配的形参表。 We'll look at the interaction of overloading and templates in more detail in the next section. For now, what's important to know is that when we define a nontemplate function, normal conversions are applied to the arguments. When we specialize a template, conversions are not applied to the argument types. In a call to a specialized version of a template, the argument type(s) in the call must match the specialized version function parameter type(s) exactly. If they don't, then the compiler will instantiate an instantiation for the argument(s) from the template definition. 下一节将更详细地介绍重载和模板的交互作用。现在,重要的是知道,当定义非模板函数的时候,对实参应用常规转换;当特化模板的时候,对实参类型不应用转换。在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不完全匹配,编译器将为实参从模板定义实例化一个实例。 Duplicate Definitions Cannot Always Be Detected不是总能检测到重复定义If a program consists of more than one file, the declaration for a template specialization must be visible in every file in which the specialization is used. A function template cannot be instantiated from the generic template definition in some files and be specialized for the same set of template arguments in other files. 如果程序由多个文件构成,模板特化的声明必须在使用该特化的每个文件中出现。不能在一些文件中从泛型模板定义实例化一个函数模板,而在其他文件中为同一模板实参集合特化该函数模板。 Ordinary Scope Rules Apply to Specializations普通作用域规则适用于特化Before we can declare or define a specialization, a declaration for the template that it specializes must be in scope. Similarly, a declaration for the specialization must be in scope before that version of the template is called: 在能够声明或定义特化之前,它所特化的模板的声明必须在作用域中。类似地,在调用模板的这个版本之前,特化的声明必须在作用域中: // define the general compare template template <class T> int compare(const T& t1, const T& t2) { /* ... */ } int main() { // uses the generic template definition int i = compare("hello", "world"); // ... } // invalid program: explicit specialization after call template<> int compare<const char*>(const char* const& s1, const char* const& s2) { /* ... */ } This program is in error because a call that would match the specialization is made before the specialization is declared. When the compiler sees a call, it must know to expect a specialization for this version. Otherwise, the compiler is allowed to instantiate the function from the template definition. 这个程序有错误,因为在声明特化之前,进行了可以与特化相匹配的一个调用。当编译器看到一个函数调用时,它必须知道这个版本需要特化,否则,编译器将可能从模板定义实例化该函数。
It is an error for a specialization to appear after a call to that instance of the template has been seen. 特化出现在对该模板实例的调用之后是错误的。 16.6.2. Specializing a Class Template16.6.2. 类模板的特化Our Queue class has a problem similar to the one in compare when used with C-style strings. In this case, the problem is in the push function. That function copies the value it's given to create a new element in the Queue. By default, copying a C-style character string copies only the pointer, not the characters. Copying a pointer in this case has all the problems that shared pointers have in other contexts. The most serious is that if the pointer points to dynamic memory, it's possible for the user to delete the array to which the pointer points. 当用于 C 风格字符串时,Queue 类具有与 compare 函数相似的问题。在这种情况下,问题出在 push 函数中,该函数复制给定值以创建 Queue 中的新元素。默认情况下,复制 C 风格字符串只会复制指针,不会复制字符。这种情况下复制指针将出现共享指针在其他环境中会出现的所有问题,最严重的是,如果指针指向动态内存,用户就有可能删除指针所指的数组。 Defining a Class Specialization定义类特化One way to provide the right behavior for Queue's of C-style strings is to define a specialized version of the entire class for const char*: 为 C 风格字符串的 Queue 提供正确行为的一种途径,是为 const char* 定义整个类的特化版本: /* definition of specialization for const char* * this class forwards its work to Queue<string>; * the push function translates the const char* parameter to a string * the front functions return a string rather than a const char* */ template<> class Queue<const char*> { public: // no copy control: Synthesized versions work for this class // similarly, no need for explicit default constructor either void push(const char*); void pop() {real_queue.pop();} bool empty() const {return real_queue.empty();} // Note: return type does not match template parameter type std::string front() {return real_queue.front();} const std::string &front() const {return real_queue.front();} private: Queue<std::string> real_queue; // forward calls to real_queue }; This implementation gives Queue a single data element: a Queue of strings. The various members delegate their work to this memberfor example, pop is implemented by calling pop on real_queue. 这个实现给了 Queue 一个数据元素:string 对象的 Queue。各个成员将它们的工作委派给这个成员。例如,通过调用 real_queue 的 pop 实现 pop 成员。 This version of the class does not define the copy-control members. Its only data element has a class type that does the right thing when copied, assigned, or destroyed; we can use the synthesized copy-control members. Queue 类的这个版本没有定义复制控制成员,它唯一的数据成员为类类型,该类类型在被复制、被赋值或被撤销时完成正确的工作。可以使用合成的复制控制成员。 Our Queue class implements mostly, but not entirely, the same interface as the template version of Queue. The difference is that we return a string rather than a char* from the front members. We do so to avoid having to manage the character array that would be required if we wanted to return a pointer. 这个 Queue 类实现了与 Queue 的模板版本大部分相同但不完全相同的接口,区别在于 front 成员返回的是 string 而不是 char*,这样做是为了避免必须管理字符数组——如果想要返回指针,就需要字符数组。 It is worth noting that a specialization may define completely different members than the template itself. If a specialization fails to define a member from the template, that member may not be used on objects of the specilization type. The member definitions of the class template are not used to create the definitions for the members of an explicit specialization. 值得注意的是,特化可以定义与模板本身完全不同的成员。如果一个特化无法从模板定义某个成员,该特化类型的对象就不能使用该成员。类模板成员的定义不会用于创建显式特化成员的定义。
Class Specialization Definition类特化定义
Our class defines only one member outside the class: 我们的类只在类的外部定义了一个成员: void Queue<const char*>::push(const char* val) { return real_queue.push(val); } Although it does little obvious work, this function implicitly copies the character array to which val points. The copy is made in the call to real_queue.push, which creates a new string from the const char* argument. That argument uses the string constructor that takes a const char*. The string constructor copies the characters from the array pointed to by val into an unnamed string that will be stored in the element we push onto real_queue. 虽然这个函数几乎没有做什么工作,但它隐式复制了 val 指向的字符数组。复制是在对 real_queue.push 的调用中进行的,该调用从 const char* 实参创建了一个新的 string 对象。const char* 实参使用了以 const char* 为参数的 string 构造函数,string 构造函数将 val 所指的数组中的字符复制到未命名的 string 对象,该对象将被存储在 push 到 real_queue 的元素中。
16.6.3. Specializing Members but Not the Class16.6.3. 特化成员而不特化类If we look a bit more deeply at our class, we can see that we can simplify our code: Rather than specializing the whole template, we can specialize just the push and pop members. We'll specialize push to copy the character array and pop to free the memory we used for that copy: 如果更深入一点分析我们的类,就能够看到代码可以简化:除了特化整个模板之外,还可以只特化 push 和 pop 成员。我们将特化 push 成员以复制字符数组,并且特化 pop 成员以释放该副本使用的内存: template <> void Queue<const char*>::push(const char *const &val) { // allocate a new character array and copy characters from val char* new_item = new char[strlen(val) + 1]; strncpy(new_item, val, strlen(val) + 1); // store pointer to newly allocated and initialized element QueueItem<const char*> *pt = new QueueItem<const char*>(new_item); // put item onto existing queue if (empty()) head = tail = pt; // queue has only one element else { tail->next = pt; // add new element to end of queue tail = pt; } } template <> void Queue<const char*>::pop() { // remember head so we can delete it QueueItem<const char*> *p = head; delete head->item; // delete the array allocated in push head = head->next; // head now points to next element delete p; // delete old head element } Now, the class type Queue<const char*> will be instantiated from the generic class template definition, with the exception of the push and pop functions. When we call push or pop on a Queue<const char*>, then the specialized version will be called. When we use any other member, the generic one will be instantiated for const char* from the class template. 现在,类类型 Queue<const char*> 将从通用类模板定义实例化而来,而 push 和 pop 函数例外。调用 Queue<const char*> 对象的 push 或 pop 函数时,将调用特化版本;调用任意其他成员时,将从类模板为 const char* 实例化一个通用版本。 Specialization Declarations特化声明Member specializations are declared just as any other function template specialization. They must start with an empty template parameter list: 成员特化的声明与任何其他函数模板特化一样,必须以空的模板形参表开头: // push and pop specialized for const char* template <> void Queue<const char*>::push(const char* const &); template <> void Queue<const char*>::pop(); These declarations should be placed in the Queue header file. 这些声明应放在 Queue 类的头文件中。
16.6.4. Class-Template Partial Specializations16.6.4. 类模板的部分特化If a class template has more than one template parameter, we might want to specialize some but not all of the template parameters. We can do so using a class template partial specialization: 如果类模板有一个以上的模板形参,我们也许想要特化某些模板形参而非全部。使用类模板的部分特化可以做到这一点: template <class T1, class T2> class some_template { // ... }; // partial specialization: fixes T2 as int and allows T1 to vary template <class T1> class some_template<T1, int> { // ... }; A class template partial specialization is itself a template. The definition of a partial specialization looks like a template definition. Such a definition begins with the keyword template followed by a template parameter list enclosed by angle brackets (<>). The parameter list of a partial specialization is a subset of the parameter list of the corresponding class template definition. The partial specialization for some_template has only one template type parameter named T1. The second template argument for T2 is known to be int. The template parameter list for the partial specialization only lists the parameters for which the template arguments are still unknown. 类模板的部分特化本身也是模板。部分特化的定义看来像模板定义,这种定义以关键字 template 开头,接着是由尖括号(<>)括住的模板形参表。部分特化的模板形参表是对应的类模板定义形参表的子集。some_template 的部分特化只有一个名为 T1 的模板类型形参,第二个模板形参 T2 的实参已知为 int。部分特化的模板形参表只列出未知模板实参的那些形参。 Using a Class-Template Partial Specialization使用类模板的部分特化The partial specialization has the same name as the class template to which it correspondsnamely, some_template. The name of the class template must be followed by a template argument list. In the previous example, the template argument list is <T1,int>. Because the argument value for the first template parameter is unknown, the argument list uses the name of the template parameter T1 as a placeholder. The other argument is the type int, for which the template is partially specialized. 部分特化与对应类模板有相同名字,即这里的 some_template。类模板的名字后面必须接着模板实参列表,前面例子中,模板实参列表是 <T1,int>。因为第一个模板形参的实参值未知,实参列表使用模板形参名 T1 作为占位符,另一个实参是类型 int,为 int 而部分特化模板。 As with any other class template, a partial specialization is instantiated implicitly when used in a program: 像任何其他类模板一样,部分特化是在程序中使用时隐式实例化: some_template<int, string> foo; // uses template some_template<string, int> bar; // uses partial specialization Notice that the type of the second variable, some_template parameterized by string and int, could be instantiated from the generic class template definition as well as from the partial specialization. Why is it that the partial specialization is chosen to instantiate the template? When a parital specialization is declared, the compiler chooses the template definition that is the most specialized for the instantiation. When no partial specialization can be used, the generic template definition is used. The instantiated type of foo does not match the partial specialization provided. Thus, the type of foo must be instantiated from the general class template, binding int to T1 and string to T2. The partial specialization is only used to instantiate some_template types with a second type of int. 注意第二个变量的类型,形参为 string 和 int 的 some_template,既可以从普通类模板定义实例化,也可以从部分特化实例化。为什么选择部分特化来实例化该模板呢?当声明了部分特化的时候,编译器将为实例化选择最特化的模板定义,当没有部分特化可以使用的时候,就使用通用模板定义。foo 的实例化类型与提供的部分特化不匹配,因此,foo 的类型必然从通用类模板实例化,将 int 绑定到 T1 并将 string 绑定到 T2。部分特化只用于实例化第二个类型为 int 的 some_template 类型。 The definition of a partial specialization is completely disjointed from the definition of the generic template. The partial specialization may have a completely different set of members from the generic class template. The generic definitions for the members of a class template are never used to instantiate the members of the class template partial specialization. 部分特化的定义与通用模板的定义完全不会冲突。部分特化可以具有与通用类模板完全不同的成员集合。类模板成员的通用定义永远不会用来实例化类模板部分特化的成员。 ![]() |