Team LiB
Previous Section Next Section

16.6. Template Specializations

16.6. 模板特化

The rest of this chapter covers a somewhat advanced topic. It can be safely skipped on first reading.

本章其余部分将介绍一个比较高级的主题,在第一次阅读时可以跳过它。



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 Template

16.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 keyword template followed by an empty bracket pair (<>),

    关键字 template 后面接一对空的尖括号(<>);

  • followed by the template name and a bracket pair specifying the template parameters(s) that this specialization defines,

    再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参;

  • the function parameter list,

    函数形参表;

  • and the function body.

    函数体。

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.

如果程序由多个文件构成,模板特化的声明必须在使用该特化的每个文件中出现。不能在一些文件中从泛型模板定义实例化一个函数模板,而在其他文件中为同一模板实参集合特化该函数模板。

As with other function declarations, declarations for template specializations should be included in a header file. That header should then be included in every source file that uses the specialization.

与其他函数声明一样,应在一个头文件中包含模板特化的声明,然后使用该特化的每个源文件包含该头文件。



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.

这个程序有错误,因为在声明特化之前,进行了可以与特化相匹配的一个调用。当编译器看到一个函数调用时,它必须知道这个版本需要特化,否则,编译器将可能从模板定义实例化该函数。

A program cannot have both an explicit specialization and an instantiation for the same template with the same set of template arguments.

对具有同一模板实参集的同一模板,程序不能既有显式特化又有实例化。



It is an error for a specialization to appear after a call to that instance of the template has been seen.

特化出现在对该模板实例的调用之后是错误的。

Exercises Section 16.6.1

Exercise 16.52:

Define a function template count to count the number of occurrences of some value in a vector.

定义函数模板 count 计算一个 vector 中某些值的出现次数。

Exercise 16.53:

Write a program to call the count function defined in the previous exercise passing it first a vector of doubles, then a vector of ints, and finally a vector of chars.

编写一个程序调用上题中定义的 count 函数,首先传给该函数一个 doublevector,然后传递一个 intvector,最后传递一个 charvector

Exercise 16.54:

Introduce a specialized template instance of the count function to handle strings. Rerun the program you wrote to call the function template instantiations.

引入 count 函数的一个特化模板实例以处理 string 对象。重新运行你所编写的调用函数模板实例化的程序。


16.6.2. Specializing a Class Template

16.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_queuepop 实现 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.

值得注意的是,特化可以定义与模板本身完全不同的成员。如果一个特化无法从模板定义某个成员,该特化类型的对象就不能使用该成员。类模板成员的定义不会用于创建显式特化成员的定义。

A class template specialization ought to define the same interface as the template it specializes. Doing otherwise will surprise users when they attempt to use a member that is not defined.

类模板特化应该与它所特化的模板定义相同的接口,否则当用户试图使用未定义的成员时会感到奇怪。



Class Specialization Definition
类特化定义

When a member is defined outside the class specialization, it is not preceded by the tokens template<>.

在类特化外部定义成员时,成员之前不能加 template<> 标记。



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 对象,该对象将被存储在 pushreal_queue 的元素中。

Exercises Section 16.6.2

Exercise 16.55:

The comments on the specialized version of Queue for const char* note that there is no need to define the default constructor or copy-control members. Explain why the synthesized members suffice for this version of Queue.

Queue 针对 const char* 的特化版本中的注释指出,不必定义默认构造函数或复制控制成员,解释为什么对于 Queue 的这个版本合成成员就足够了。

Exercise 16.56:

We explained the generic behavior of Queue if it is not specialized for const char*. Using the generic Queue template, explain what happens in the following code:

我们已经解释过未针对 const char* 特化的 Queue 的泛型行为,使用泛型 Queue 模板解释下面代码中会发生什么:

     Queue<const char*> q1;
     q1.push("hi"); q1.push("bye"); q1.push("world");
     Queue<const char*> q2(q1); // q2 is a copy of q1

     Queue<const char*> q3;     // empty Queue
     q1 = q3;

In particular, say what the values of q1 and q2 are after the initialization of q2 and after the assignment to q3.

具体而言,就是说明在 q2 的初始化和 q3 的赋值之后,q1q2 是什么值。

Exercise 16.57:

Our specialized Queue returns strings from the front function rather than const char*. Why do you suppose we did so? How might you implement the Queue to return a const char*? Discuss the pros and cons of each approach.

我们的 Queue 特化版本从 front 函数返回 string 对象而不是 const char*,你认为为什么这样做?你能够怎样实现 Queue 以返回 const char*?讨论每种方法的优缺点。


16.6.3. Specializing Members but Not the Class

16.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:

如果更深入一点分析我们的类,就能够看到代码可以简化:除了特化整个模板之外,还可以只特化 pushpop 成员。我们将特化 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*> 将从通用类模板定义实例化而来,而 pushpop 函数例外。调用 Queue<const char*> 对象的 pushpop 函数时,将调用特化版本;调用任意其他成员时,将从类模板为 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 类的头文件中。

Exercises Section 16.6.3

Exercise 16.58:

The specialization of Queue presented in the previous subsection and the specialization in this subsection of push and pop apply only to Queues of const char*. Implement these two different ways of specializing Queue that could be used with plain char*.

前一小节中给出的 Queue 类的特化,以及本小节中 pushpop 函数的特化,只适用于 const char* 类型的 Queue。为普通 char* 实现特化 Queue 的这两种不同方式。

Exercise 16.59:

If we go the route of specializing only the push function, what value is returned by front for a Queue of C-style character strings?

如果走只特化 push 函数的路线,对于 C 风格字符串的 Queuefront 返回什么值?

Exercise 16.60:

Discuss the pros and cons of the two designs: defining a specialized version of the class for const char* versus specializing only the push and pop functions. In particular, compare and contrast the behavior of front and the possibility of errors in user code corrupting the elements in the Queue.

讨论这两个设计的优缺点:为 const char* 定义该类的特化版本和只特化 pushpop 函数。具体而言,比较 front 的行为的异同以及用户代码中的错误破坏 Queue 元素的可能性。


16.6.4. Class-Template Partial Specializations

16.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.

注意第二个变量的类型,形参为 stringintsome_template,既可以从普通类模板定义实例化,也可以从部分特化实例化。为什么选择部分特化来实例化该模板呢?当声明了部分特化的时候,编译器将为实例化选择最特化的模板定义,当没有部分特化可以使用的时候,就使用通用模板定义。foo 的实例化类型与提供的部分特化不匹配,因此,foo 的类型必然从通用类模板实例化,将 int 绑定到 T1 并将 string 绑定到 T2。部分特化只用于实例化第二个类型为 intsome_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.

部分特化的定义与通用模板的定义完全不会冲突。部分特化可以具有与通用类模板完全不同的成员集合。类模板成员的通用定义永远不会用来实例化类模板部分特化的成员。

Team LiB
Previous Section Next Section