Oracle Solaris Studio 12.2:C++ 用户指南

6.7 模板问题部分

本节描述了使用模板时会遇到的问题。

6.7.1 非本地名称解析和实例化

有时模板定义使用模板参数或模板本身未定义的名称。如此,编译器解决了封闭模板作用域的名称,该模板可以在定义或实例化点的上下文中。名称可以在不同的位置具有不同的含义,产生不同的解析。

名称解析比较复杂。因此,您不应该依赖除一般全局环境中提供的名称外的非本地名称。也就是说,仅使用在任何地方都用相同方法声明和定义的非本地名称。在以下示例中,模板函数 converter 使用了非本地名称 intermediarytemporary。在 use1.ccuse2.cc 中这些名称的定义不同,因此在不同的编译器下可能会生成不同的结果。为了能可靠地使用模板,所有非本地名称(该示例中为 intermediarytemporary)在任何地方都必须有相同的定义。


use_common.h
// Common template definition
template <class Source, class Target>
Target converter(Source source)
       {temporary = (intermediary)source;
       return (Target)temporary;}
use1.cc
typedef int intermediary;
int temporary;

#include "use_common.h"
use2.cc
typedef double intermediary;
unsigned int temporary;

#include "use_common.h"

一个常见的非本地名称用法是在模板内使用 cincout 流。有时程序员要将流作为模板参数传递,这时就要引用到全局变量。但 cincout 在任何地方都必须有相同的定义。

6.7.2 作为模板参数的本地类型

模板实例化系统取决于类型名称,等效于决定哪些模板需要实例化或重新实例化。因此本地类型用作模板参数时,会导致严重的问题。小心在代码中也出现类似的问题。例如:


示例 6–1 本地类型用作模板参数问题的示例


array.h
template <class Type> class Array {
        Type* data;
        int   size;
    public:
        Array(int sz);
        int GetSize();
};

array.cc
template <class Type> Array<Type>::Array(int sz)
    {size = sz; data = new Type[size];}
template <class Type> int Array<Type>::GetSize()
    {return size;}

file1.cc
#include "array.h"
struct Foo {int data;};
Array<Foo> File1Data(10);

file2.cc
#include "array.h"
struct Foo {double data;};
Array<Foo> File2Data(20);

file1.cc 中记录的 Foo 类型与在 file2.cc 中记录的 Foo 类型不同。以这种方法使用本地类型会出现错误和意外的结果。

6.7.3 模板函数的友元声明

模板在使用之前必须先声明。模板的使用由友元声明构成,不是由模板的声明构成。实际的模板声明必须在友元声明之前。例如,编译系统尝试链接以下示例中生成的目标文件时,对实例化的 operator<< 函数,会生成未定义错误。


示例 6–2 友元声明问题的示例


array.h
// generates undefined error for the operator<< function
#ifndef ARRAY_H
#define ARRAY_H
#include <iosfwd>

template<class T> class array {
    int size;
public:
    array();
    friend std::ostream&
        operator<<(std::ostream&, const array<T>&);
};
#endif

array.cc
#include <stdlib.h>
#include <iostream>

template<class T> array<T>::array() {size = 1024;}

template<class T>
std::ostream&
operator<<(std::ostream& out, const array<T>& rhs)
    {return out <<’[’ << rhs.size <<’]’;}

main.cc
#include <iostream>
#include "array.h"

int main()
{
    std::cout
      << "creating an array of int... " << std::flush;
    array<int> foo;
    std::cout << "done\n";
    std::cout << foo << std::endl;
    return 0;
}

请注意,因为编译器将以下代码作为普通函数(array 类的 friend)的声明进行读取,所以编译期间不会出现错误消息。


friend ostream& operator<<(ostream&, const array<T>&);

因为 operator<< 实际上是模板函数,所以需要在声明 template class array 之前提供模板声明。但是,由于 operator<< 有一个 array<T> 类型的参数,因此必须在声明函数之前声明 array<T>。文件 array.h 必须如下所示:


#ifndef ARRAY_H
#define ARRAY_H
#include <iosfwd>

// the next two lines declare operator<< as a template function
template<class T> class array;
template<class T>
    std::ostream& operator<<(std::ostream&, const array<T>&);

template<class T> class array {
    int size;
public:
    array();
    friend std::ostream&
      operator<< <T> (std::ostream&, const array<T>&);
};
#endif

6.7.4 在模板定义内使用限定名称

C++ 标准要求使用具有限定名的类型,这些限定名取决于要用 typename 关键字显式标注为类型名称的模板参数。即使编译器“知道”它应该是一个类型,也是如此。以下示例中的注释说明了具有要用 typename 关键字的限定名的类型。


struct simple {
  typedef int a_type;
  static int a_datum;
};
int simple::a_datum = 0; // not a type
template <class T> struct parametric {
  typedef T a_type;
  static T a_datum;
};
template <class T> T parametric<T>::a_datum = 0;   // not a type
template <class T> struct example {
  static typename T::a_type variable1;             // dependent
  static typename parametric<T>::a_type variable2; // dependent
  static simple::a_type variable3;                 // not dependent
};
template <class T> typename T::a_type             // dependent
  example<T>::variable1 = 0;                      // not a type
template <class T> typename parametric<T>::a_type // dependent
  example<T>::variable2 = 0;                      // not a type
template <class T> simple::a_type   // not dependent
example<T>::variable3 = 0;          // not a type

6.7.5 嵌套模板名称

由于 ">>" 字符序列解释为右移运算符,因此在一个模板名称中使用另一个模板名称时必须小心。确保相邻的 ">" 字符之间至少有一个空格。

例如,以下是形式错误的语句:


Array<String<10>> short_string_array(100); // >> = right-shift

被解释为:


Array<String<10 >> short_string_array(100);

正确的语法为:


Array<String<10> > short_string_array(100);

6.7.6 引用静态变量和静态函数

在模板定义中,编译器不支持引用在全局作用域或名称空间中声明为静态的对象或函数。如果生成了多个实例,则每个示例引用到了不同的对象,因此违背了单次定义规则(C++ 标准的 3.2 节)。通常的失败指示是链接时丢失符号。

如果想要所有模板实例化共享单一对象,那么请使对象成为已命名空间的非静态成员。如果想要模板类的每个实例化不同对象,那么请使对象成为模板类的静态成员。如果希望每个模板函数实例化的对象不同,请使对象成为函数的本地对象。

6.7.7 在同一目录中使用模板生成多个程序

如果要通过指定 -instances=extern 生成多个程序或库,建议在不同的目录中生成这些程序或库。如果要在同一目录中生成多个程序,那么您需要清除不同生成程序之间的系统信息库。这样可以避免出现任何不可预料的错误。有关更多信息,请参见7.4.4 共享模板系统信息库

考虑以下示例的 make 文件 a.ccb.ccx.hx.cc。请注意,仅当指定了 -in tances=extern 时,此示例才有意义:


........
Makefile
........
CCC = CC

all: a b

a:
    $(CCC) -I. -instances=extern -c a.cc
    $(CCC) -instances=extern -o a a.o

b:
    $(CCC) -I. -instances=extern -c b.cc
    $(CCC) -instances=extern -o b b.o

clean:
    /bin/rm -rf SunWS_cache *.o a b

...
x.h
...
template <class T> class X {
public:
  int open();
  int create();
  static int variable;
};

...
x.cc
...
template <class T> int X<T>::create() {
  return variable;
}

template <class T> int X<T>::open() {
  return variable;
}

template <class T> int X<T>::variable = 1;

...
a.cc
...
#include "x.h"

int main()
{
  X<int> temp1;

  temp1.open();
  temp1.create();
}

...
b.cc
...
#include "x.h"

int main()
{
  X<int> temp1;

  temp1.create();
}

如果同时生成了 ab,请在两个生成之间添加 make clean。以下命令会引起错误:


example% make a
example% make b

以下命令不会产生任何错误:


example% make a
example% make clean
example% make b