为什么模板类不支持分离编译


首先,C++标准中提到,一个编译单元[translation unit]是指一个.cpp文件以及它所include的所有.h文件,.h文件里的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件,后者拥有PE[Portable Executable,即windows可执行文件]文件格式,并且本身包含的就已经是二进制码,但是,不一定能够执行,因为并不保证其中一定有main函数。当编译器将一个工程里的所有.cpp文件以分离的方式编译完毕后,再由连接器(linker)进行连接成为一个.exe文件。
    然而,对于模板,你知道,模板函数的代码其实并不能直接编译成二进制代码,其中要有一个“具现化”的过程。举个例子:
//----------main.cpp------//
 template<class T>
 void f(T t)
 {}
 int main()
 {
   …//do something
   f(10); //call f<int> 编译器在这里决定给f一个f<int>的具现体
   …//do other thing
  }
也就是说,如果你在main.cpp文件中没有调用过f,f也就得不到具现,从而main.obj中也就没有关于f的任意一行二进制代码!!如果你这样调

用了:
  f(10); //f<int>得以具现化出来
f(10.0); //f<double>得以具现化出来
这样main.obj中也就有了f<int>,f<double>两个函数的二进制代码段。以此类推
然而具现化要求编译器知道模板的定义,不是吗?
看下面的例子:[将模板和它的实现分离]
  //-------------test.h----------------//
    template<class T>
    class A
    {
     public:
        void f(); //这里只是个声明
     };
//---------------test.cpp-------------//
  #include”test.h”
  template<class T>
  void A<T>::f()  //模板的实现,但注意:不是具现
  {
    …//do something
  }
//---------------main.cpp---------------//
   #include”test.h”
   int main()
   {
      A<int> a;
      a.f();
   //编译器在这里并不知道A<int>::f的定义,因为它不在test.h里面
   //于是编译器只好寄希望于连接器,希望它能够在其他.obj里面找到
   //A<int>::f的实现体,在本例中就是test.obj,然而,后者中真有A<int>::f的
   //二进制代码吗?NO!!!因为C++标准明确表示,当一个模板不被用到的时
   //侯它就不该被具现出来,test.cpp中用到了A<int>::f了吗?没有!!所以实
   //际上test.cpp编译出来的test.obj文件中关于A::f的一行二进制代码也没有
   //于是连接器就傻眼了,只好给出一个连接错误
   //但是,如果在test.cpp中写一个函数,其中调用A<int>::f,则编译器会将其//具现出来,因为在这个点上[test.cpp中],编译器知道模

板的定义,所以能//够具现化,于是,test.obj的符号导出表中就有了A<int>::f这个符号的地
//址,于是连接器就能够完成任务。
   } 关键是:在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找[当遇到未决符号时它会寄希望 于连接器]。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会具现化出来,所以,当编译器只看 到模板的声明时,它不能具现化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板 的.cpp文件中没有用到模板的具现体时,编译器懒得去具现,所以,整个工程的.obj中就找不到一行模板具现体的二进制代码,于是连接器也傻眼
解决方法:
1. 将类模板申明与类模板定义放到一个文件中。
2. 在主函数中加 #include"implement.cpp"。
3. 使用export关键字。


猜你喜欢

转载自blog.csdn.net/fanjiule/article/details/80995501