C++模板类(函数)编译 多文件编译

在C++中,模版和普通的函数或类有很多不一样的性质。前两天写了一个模版类,头文件和实现文件分开存放的。这就出问题了,老是说找不到实现。查了一些资料才知道,原因是这样的,编译器在实例化一个类时,需要知道该类的所有确定的信息,如果是普通的类这是完全由头文件(.h)中类的声明决定的。但是对于模版类,此信息不确定,于是编译器只是存放一个符号,而把这一个步骤放到最后链接时来完成。而编译器在编译模版类的实现文件(.cpp)时没有发现其他地方有这个类的实例化。最终,到链接阶段找不到类模版的实例,出错。


解决的办法有两个,把类的实现文件放到声明文件中(或者在声明文件的最后include一下),另一个方法就是在实现时在函数头部加上export(不过此方法不是所有的编译器都支持,在GCC编译器下就出现了--警告: 关键字‘export’未实现,将被忽略 [默认启用]--)。


=================================================================================


为了访问其他编译单元(如另一代码文件)中的变量或对象,对普通类型(包括基本数据类、结构和类),可以利用关键字extern,来使用这些变量或对象时;但是对模板类型,则必须在定义这些模板类对象和模板函数时,使用标准C++新增加的关键字export(导出/出口/输出)。例如:

extern int n;

extern struct Point p;

extern class A a;

export template<class T> class Stack<int> s;

export template<class T> void f (T& t) {……}

一般是在头文件中给出类的定义或全局函数的声明信息,而在代码文件中给出具体的(类成员函数或全局函数的)函数定义。然后在多个用户代码文件中包含该头文件后,就可以使用其中定义或声明的类和函数。头文件中一般不包含变量、结构和类对象的定义,因为这样可能会导致重复定义的编译错误。解决办法是,在某个代码文件中进行定义,在其他用户代码文件中用extern来引用它们。

但是对模板类型,则可以在头文件中,声明模板类和模板函数;在代码文件中,使用关键字export来定义具体的模板类对象和模板函数;然后在其他用户代码文件中,包含声明头文件后,就可以使用该这些对象和函数了。例如:

// out.h:(声明头文件——只包含out函数的声明信息)

template<class T> void out (const T& t);

// out.cpp:(定义代码文件——包含out函数的声明[通过include]和定义等全部信息)

#include <iostream>

#include “out.h”

export template<class T> void out (const T& t) {std::cerr << t;}

//user.cpp:(用户代码文件——包含函数的声明头文件后就可以使用该函数)

#include “out.h”

// 使用out()

说明:VC05目前还不支持export关键字(的编译)。

===================================================================================================================================




这应该是令许多常用 VC6 的C++初学者头疼的问题,我自然不例外了,特此搜罗了相关问题分析。

********************************************************************************************************

大部分编译器在编译模板时都使用包含模式  
也就是一般使用的把模板放到头文件中在包含

当你不使用这个模版函数或模版类
编译器并不实例化它
当你使用时,编译器需要实例化它,
因为编译器是一次只能处理一个编译单元
也就是一次处理一个cpp文件
所以实例化时需要看到该模板的完整定义
所以都放在头文件中


这不同于普通的函数,
在使用普通的函数时,编译时只需看到该函数的声明即可编译
而在链接时由链接器来确定该函数的实体  

********************************************************************************************************

temp.h:
void fun(T);

temp.cpp:
#include "temp.h"
void fun(T){}

main.cpp:
#include "temp.h"
void main()
{
int a;
fun(a);
}

由于main.cpp用到了fun(a),所以在编译main.cpp的时候,
编译器知道它要用int来实例化fun(T)中的T,也就是要实例化fun(int),而要实例化一个函数模板就必须要知道这个函数模板的定义,但是由于main.cpp和包含函数模板定义的temp.cpp是分开编译的,所以编译器在编译main.cpp的时候就不能够用int来实例化fun(T),这样编译器就只能够把
fun<int>(a)当成一个外部符号,交由linker来resolve。
另外编译器在编译temp.cpp的时候,同样因为temp.cpp和main.cpp是分开编译的,所以此时编译器并不知道main.cpp中用到了fun<int>(a),所以此时编译器虽然知道函数模板的定义,但也不会去实例化任何fun,当然就不会用int来实例化fun<T>了。
结果就是,你在main.obj引用了外部符号fun<int>,但是在temp.obj中不存在fun<int>,
所以linker就会报告无法解析的外部符号了

********************************************************************************************************

如果要把.h文件和.cpp文件分开,需要使用export关键字

要实例化一个函数模板就必须要知道这个函数模板的定义

********************************************************************************************************

C++ Primer 第三版 中文版
10.5 模板编译模式:
"C++支持两种模板编译模式包含模式Inclusion Model 和分离模式Separation Model"
10.5.1 包含编译模式
"在包含编译模式下我们在每个模板被实例化的文件中包含函数模板的定义并且往往把定义放在头文件中像对内联函数所做的那样"
10.5.2 分离编译模式
"在分离编译模式下函数模板的声明被放在头文件中"
"在模板定义中有一个关键字export"
"关键字export 告诉编译器在生成被其他文件使用的函数模板实例时可能需要这个模板定义编译器必须保证在生成这些实例时该模板定义是可见的"
"关键字export 不需要出现在头文件的模板声明中"
"分离模式使我们能够很好地将函数模板的接口同其实现分开进而组织好
程序以便把函数模板的接口放到头文件中而把实现放在文本文件中但是并不是所有的编译器都支持分离模式即使支持也未必总能支持得很好支持分离模式需要更复杂的程序设计环境所以它们不能在所有C++编译器实现中提供"
"Inside the C++ Object Model 描述了一个C++编译器the Edison Design Group compiler支持的模板实例化机制"

很遗憾,
目前VC的任何版本(visual studio 2005未知)皆不支持分离模式!


阅读更多

没有更多推荐了,返回首页