Sun Studio 12 Update 1:C++ 用户指南

第 7 章 编译模板

C++ 编译器在模板编译方面处理的工作要比传统 UNIX 编译器处理的工作多。C++ 编译器必须按需为模板实例生成目标代码。该编译器会使用模板系统信息库在多个独立的编译间共享模板实例,此外还接受某些模板编译选项。编译器必须在各个源文件中定位模板定义,并维护模板实例和主线代码之间的一致性。

7.1 冗余编译

如果使用标志 -verbose=template,C++ 编译器会在编译模板期间通知重要事件。但如果使用缺省值 -verbose=no%template,编译器不会发出通知。+w 选项可以在进行模板实例化时提供其他有关潜在问题的指示信息。

7.2 系统信息库管理

CCadmin(1) 命令管理模板系统信息库(只能与选项 -instances=extern 一起使用)。例如,程序中的更改会造成某些实例化过度,这样会浪费存储空间。CCadmin – clean 命令(以前是 ptclean)清除所有实例及关联数据。实例化仅在需要时才重新创建。

7.2.1 生成的实例

为了生成模板实例,编译器将内联模板函数看作内联函数。编译器像管理其他内联函数一样管理这些内联模板函数,另外本章中的说明不适用于模板内联函数。

7.2.2 整个类实例化

编译器通常是分别实例化各个模板类成员,因此,编译器仅实例化程序中使用的成员。仅用于调试器的方法会因此而不正常地实例化。

有两种方法确保调试成员可用于调试器。

ISO C++ 标准允许开发人员编写模板类,因为并不是所有成员都可以使用模板参数。只要非法成员未被实例化,程序就仍然完好。ISO C++ 标准库使用了这种技术。但是,-template=wholeclass 选项会实例化所有成员,因此不能用于此类使用有问题的模板参数实例化的模板类。

7.2.3 编译时实例化

实例化是 C++ 编译器从模板创建可用的函数或对象的过程。C++ 编译器使用了编译时实例化,在编译对模板的引用时强制进行实例化。

编译时实例化的优点是:

如果源文件位于不同的目录或您使用了具有模板符号的库,则模板可以多次实例化。

7.2.4 模板实例的放置和链接

缺省情况下,实例会进入特殊地址区域,链接程序会识别并丢弃重复项。您可以指示编译器使用五个实例放置和链接方法之一: 外部、静态、全局、显式和半显式。

本节讨论了五种实例放置和链接方法。6.3 模板实例化中提供了有关生成实例的其他信息。

7.3 外部实例

对于外部实例方法,所有实例都放置在模板系统信息库中。编译器确保只有一个一致的模板实例存在;这些实例既不是未定义的也不是多重定义的。模板仅在需要时才重新实例化。对于非调试代码,所有目标文件(包括模板缓存中的任何目标文件)在使用 -instances=extern 时的大小总量小于在使用 -instances=global 时的大小总量。

模板实例接受系统信息库中的全局链接实例是使用外部链接从当前编译单元引用的。


注 –

如果在不同的步骤中进行编译和链接,并且在编译步骤中指定了 -instance=extern,则还必须在链接步骤中指定该选项。


这种方法的缺点是更改程序或程序发生重大更改时必须清除缓存。高速缓存是并行编译的瓶颈,这与使用 dmake 时一样,因为每次只能有一个编译访问高速缓存。另外,每个目录内仅能生成一个程序。

决定缓存中是否存在有效的模板实例比直接在主目标文件中创建实例(如果需要,用完后可以丢弃)要花费更长的时间。

可使用 -instances=extern 选项指定外部链接。

因为实例存储在模板系统信息库中,所以必须使用 CC 命令将使用外部实例的 C++ 对象链接到程序中。

如果要创建包含了使用的所有模板实例的库,请结合使用 CC 命令与 — xar 选项。而要使用 ar 命令。例如:


example% CC– xar -instances=extern– o libmain.a a.o b.o c.o

有关更多信息,请参见表 15–3

7.3.1 可能的缓存冲突

如果指定了 -instance=extern,请勿在同一目录中运行不同的编译器版本,以避免可能的高速缓存冲突。使用 -instances=extern 模板模型时,请注意:

7.3.2 静态实例


注 –

-instances=static 选项已过时。没有任何理由再使用 -instances=static,因为 -instances=global 现在提供了静态的所有优点而没有其缺点。早期的编译器中提供了此选项来克服现已不存在的问题。


对于静态实例方法,所有实例都被放置在当前编译单元内。因此,模板在每个重新编译期间重新实例化;这些实例不保存到模板系统信息库。

这种方法的缺点是不遵循语言语义,并且会生成很大的对象和可执行文件。

实例接收静态链接。这些实例在当前编译单元外部是不可视的或不可用的。因此,模板可以在多个目标文件中具有相同的实例化。因为多重实例产生了不必要的大程序,所以对于不可能多重实例化模板的小程序可以使用静态链接。

静态实例的编译速度很快,因此这种方法也适用于修复并继续方式的调试。(请参见《使用 dbx 调试程序》。)


注 –

如果您的程序取决于多个编译单元间的共享模板实例(例如模板类或模板函数的静态数据成员),请勿使用静态实例方法。否则程序会工作不正常。


可使用 -instances=static 编译器选项指定静态实例链接。

7.3.3 全局实例

与早期的编译器发行版不同,现在不必预防出现一个全局实例有多个副本。

这种方法的优点是通常由其他编译器接受的不正确源代码也能在这种模式中接受。特别的是,从模板实例内对静态变量的引用是不合法的,但通常是可以接受的。

这种方法的缺点是单个目标文件会很大,原因是多个文件中模板实例有多个副本。如果编译目标文件以便进行调试时,有些使用了 -g 选项,而有些没有使用该选项,则很难预测是获得链接到程序中模板实例的调试版本还是非调试版本。

模板实例接收全局链接。这些实例在当前编译单元外部是可视的和可用的。

可使用 -instances=global 选项(这是缺省值)指定全局实例。

7.3.4 显式实例

在显式实例方法中,仅为显式实例化的模板生成实例。隐式实例化不能满足该要求。实例被放置在当前编译单元内。

这种方法的优点是拥有最少的模板编译和最小的对象大小。

缺点是您必须手动执行所有的实例化。

模板实例接收全局链接。这些实例在当前编译单元外部是可视的和可用的。链接程序识别并丢弃重复项目。

可使用 -instances=explicit 选项指定显式实例。

7.3.5 半显式实例

使用半显式实例方法时,仅为显式实例化或模板体内隐式实例化的模板生成实例。那些被显式创建实例所需要的实例将会自动生成。主线代码中隐式实例化不满足该要求。实例被放置在当前编译单元内。因此,模板在每个重新编译期间重新实例化;生成的实例接收全局链接,且不会被保存到模板系统信息库中。

可使用 -instances=semiexplicit 选项指定半显式实例。

7.4 模板系统信息库

模板系统信息库中存储需单独进行编译的模板实例,以便仅在需要时编译模板实例。模板系统信息库包含了使用外部实例方法时模板实例化所需的所有非源文件。系统信息库不用于其他种类的实例。

7.4.1 系统信息库结构

缺省情况下,模板系统信息库位于名为 SunWS_cache 的高速缓存目录中。

缓存目录包含在放置目标文件的目录中。可以通过设置环境变量 SUNWS_CACHE_NAME 更改高速缓存目录的名称。请注意,SUNWS_CACHE_NAME 变量值必须是目录名称,而不能是路径名。这是因为编译器自动将模板缓存目录放置到了目标文件目录下,因此编译器已经具有了路径。

7.4.2 写入模板系统信息库

编译器必须存储模板实例时,编译器将模板实例存储在对应于输出文件的模板系统信息库中。例如,以下命令行会将目标文件写入 ./sub/a.o 并将模板实例写入包含在 ./sub/SunWS_cache 中的系统信息库。如果缓存目录不存在,且编译器需要实例化模板,则编译器将创建目录。


example% CC -o sub/a.o a.cc

7.4.3 从多模板系统信息库读取

编译器从对应于编译器读取的目标文件的模板系统信息库读取。即,以下命令行从 ./sub1/SunWS_cache./sub2/SunWS_cache 读取,必要时,向 ./SunWS_cache 写入。


example% CC sub1/a.o sub2/b.o

7.4.4 共享模板系统信息库

系统信息库中的模板不得违反 ISO C++ 标准的一次定义规则。也就是说,使用所有的模板时模板必须具有相同的源。违反该规则会产生不可预料的行为。

确保不违反该规则的最简单和最保守的方法是在任何一个目录内仅生成一个程序或库。两个不相关的程序可以使用相同类型的名称或外部名称来表示不同的内容。如果程序共享模板系统信息库,则模板定义会出现冲突,会产生不可预料的结果。

7.4.5 通过 -instances=extern 实现模板实例自动一致

如果指定了 -instances=extern,模板系统信息库管理器可确保系统信息库中实例的状态与源文件一致且是最新的。

例如,如果使用 – g 选项编译源文件(调试),也会使用 – g 编译来自数据库中的所需文件。

此外,模板系统信息库会跟踪编译中的更改。例如,如果设置了 — DDEBUG 标志来定义名称 DEBUG,数据库中会记录下该信息。如果在以后的编译中省略该标志,则编译器重新实例化设置依赖性的这些模板。


注 –

如果删除模板的源代码或停止使用模板,模板的实例会保留在缓存中。如果更改函数模板的签名,使用旧签名的实例会保留在缓存中。如果因为这些问题在编译时或链接时遇到了异常行为,请清除模板缓存并重新生成程序。


7.5 模板定义搜索

使用独立定义模板组织时,模板定义在当前编译单元不可用,编译器必须搜索该定义。本节描述了编译器如何找到定义。

定义搜索有些复杂,并且很容易出现错误。因此如果可能,您应该使用定义包括模板文件组织。这样有助于避免一起定义搜索。请参见5.2.1 包括的模板定义


注 –

如果使用 -template=no%extdef 选项,编译器将不搜索单独的源文件


7.5.1 源文件位置约定

如果没有随选项文件一起提供的特定方向,则编译器使用 Cfront 样式的方法来定位模板定义文件。此方法要求模板定义文件包含的基名与模板声明文件包含的基名相同。此方法也要求模板定义文件位于当前 include 路径中。例如,如果模板函数 foo() 位于 foo.h 中,匹配的模板定义文件应该命名为 foo.cc 或某些其他可识别的源文件扩展名(.C.c.cc.cpp.cxx.c++)。模板定义文件必须位于常规的 include 目录之一中,或位于与其匹配的头文件所在目录中。

7.5.2 定义搜索路径

可以用另外一种方法替代用 –I 设置的常规搜索路径,即使用选项 –ptidirectory 指定模板定义文件的搜索目录。多个 -pti 标志定义多个搜索目录-即搜索路径。如果使用 -ptidirectory,则编译器在该路径查找模板定义文件并忽略 –I 标志。由于 –ptidirectory 标志会使源文件的搜索规则变得复杂,因此应使用 –I 选项而不是 –ptidirectory 选项。

7.5.3 诊断有问题的搜索

有时,编译器会生成令人费解的警告或错误消息,因为它会查找您不打算编译的文件。此问题通常是由于某个文件(如 foo.h)包含模板声明,且隐式包含了另一个文件(如 foo.cc)。

如果头文件 foo.h 有模板声明,缺省情况下,编译器会搜索名为 foo 且具有 C++ 文件扩展名(.C、.c、.cc、.cpp.cxx 或 .c++)的文件。如果找到这样的文件,编译器将自动把它包含进来。有关这些搜索的更多信息,请参见7.5 模板定义搜索

如果有一个不打算这样处理的文件 foo.cc,有两种解决方法: