C++函数模板与分离编译模式

1.分离编译模式
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件连接起来形成单一的可执行文件的过程称为分离编译模式。

2.使用函数模板在链接时出错

在C++程序设计中,在一个源文件中定义某个函数,然后在另一个源文件中使用该函数,这是一种非常普遍的做法。但是,如果定义和调用一个函数模板时也采用这种方式,会发生编译错误。

下面的程序由三个文件组成:func.h用来对函数模板进行申明,func.cpp用来定义函数模板,main.cpp包含func.h头文件并调用相应的函数模板。

/***func.h***/
template<class T> void func(const T&);
/***end func.h***/
/***func.cpp***/
#include 

#include "func.h"
template<class T> void func(const T& t)
{
     
 std::cout<<t<<std::endl;
}
/***end func.cpp***/
/***main.cpp***/
#include 
#include "func.h"

int main()
{
     
 func(3);
}
/***end main.cpp***/

这是一个结构非常清晰的程序,但是它不能通过编译。在VS2017下的出错信息是:

error LNK2019: 无法解析的外部符号 "void __cdecl func< int>(int const &)" (??$func@H@@YAXABH@Z)

原因出现在分离编译模式上。在分离编译模式下,func.cpp会生成一个目标文件为func.obj,由于在func.cpp文件中,并没有发生函数模板调用,所以不会将函数模板func实例化为模板函数func,也就是说,在func.obj中无法找到关于模板函数func的实现代码

在源文件main.cpp中,虽然函数模板被调用,但由于没有模板代码,也不能将其实例化。也就是说,在main.obj中也找不到模板函数func的实现代码。这样,在链接的时候就会出现func没有定义的错误

3.解决办法

3.1 将函数模板的定义放到头文件

一个简单的解决办法就是将函数模板func的定义写到头文件func.h中。这样的话,只要包含了这个头文件,就会把函数模板的代码包含进来,一旦发生函数调用,就可以依据函数模板代码将其实例化。该方法使用在常见的库中,例如boost、dlib等。

这个办法虽然简单可行,但是有如下不足:

  • 函数模板的定义写进了头文件,暴露了函数模板的实现细节。
  • 不符合分离编译模式的规则,因为分离编译模式要求函数原型申明放在头文件,定义放在源文件。

注意: 这样做,如果在多个目标文件中存在相同的函数模板实例化后的模板函数实体,链接时并不会报函数重定义的错误,这与普通函数不同,因为编译器会对实例化后的重复的模板函数实体进行优化,只保留一份代码实体。如果不同的源文件中都保留一份函数模板实体,会造成代码冗余,实际上,这也是一种代码冗余的解决办法。

3.2仍然采用分离编译模式

有什么办法可以让函数模板实例化时能够找到相应的模板函数的代码呢?一个可能的解决办法就是使用关键字export。也就是说,在func.cpp里定义函数模板的时候,将函数模板头写成:

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

这样做的目的是告诉编译器,这个函数模板可能再其他源文件中被实例化。这是一个对程序员来说负担最轻的解决办法,但是,目前几乎所有的编译器都不支持关键字export,包括VC++和GNU C++。

3.3显示实例化 (推荐)

显示实例化也称为外部实例化。在不发生函数调用的时候将函数模板实例化,或者在不使用类模板的时候将类模板实例化称之为模板显示实例化。

上面遇到的问题是main.obj和func.obj中找不到模板函数func的实现代码,那么就在func.cpp中将函数模板func显示实例化为模板函数func

template void func<int>(const int&); //函数模板显示实例化

这样,就可以在func.cpp产生模板函数func的实例化代码,编译之后就会产生函数的二进制代码,供其它源文件链接,程序就可以正常运行。当类模板的成员函数的实现定义在源文件中,通过模板类的对象调用成员函数时也会出现找不到函数定义的错误,可以使用同样的方法解决,不再赘述。

ref: https://www.jb51.net/article/193095.htm


模板分离编译,建议需要将用到的所有类型的模板在cpp中显式实例化,
当编译该cpp文件时,将会实例化对应类型的模板函数
最终在对应的obj中生成符号链接,能够被其他模块调用。
否则会出现无法链接的情况。

你可能感兴趣的:(C++,c++,模板分离,模板编译,无法解析的外部符号)