C++模板函数在头文件中编译但不实现

fli*_*ies 26 c++ templates stl g++ vector

我正在尝试学习模板,我遇到了这个混淆错误.我在头文件中声明了一些函数,我想创建一个单独的实现文件来定义函数.这是调用标头的代码(dum.cpp):

#include <iostream>
#include <vector>
#include <string>
#include "dumper2.h"

int main() {
    std::vector<int> v;
    for (int i=0; i<10; i++) {
        v.push_back(i);
    }
    test();
    std::string s = ", ";
    dumpVector(v,s);
}
Run Code Online (Sandbox Code Playgroud)

现在,这是一个工作头文件(dumper2.h):

#include <iostream>
#include <string>
#include <vector>

void test();

template <class T> void dumpVector( std::vector<T> v,std::string sep);

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
    typename std::vector<T>::iterator vi;

    vi = v.begin();
    std::cout << *vi;
    vi++;
    for (;vi<v.end();vi++) {
        std::cout << sep << *vi ;
    }
    std::cout << "\n";
    return;
}
Run Code Online (Sandbox Code Playgroud)

使用实现(dumper2.cpp):

#include <iostream>
#include "dumper2.h"

void test() {
    std::cout << "!olleh dlrow\n";
}
Run Code Online (Sandbox Code Playgroud)

奇怪的是,如果我将定义dumpVector的代码从.h移动到.cpp文件,我会收到以下错误.

g++ -c dumper2.cpp -Wall -Wno-deprecated
g++ dum.cpp -o dum dumper2.o -Wall -Wno-deprecated
/tmp/ccKD2e3G.o: In function `main':
dum.cpp:(.text+0xce): undefined reference to `void dumpVector<int>(std::vector<int, std::allocator<int> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >)'
collect2: ld returned 1 exit status
make: *** [dum] Error 1
Run Code Online (Sandbox Code Playgroud)

那为什么它以一种方式而不是另一种方式工作?显然编译器可以找到test(),为什么不能找到dumpVector

Joh*_*ing 36

您遇到的问题是编译器不知道要实例化哪个版本的模板.当您将函数的实现移动到x.cpp时,它与main.cpp位于不同的转换单元中,而main.cpp无法链接到特定的实例化,因为它在该上下文中不存在.这是C++模板的一个众所周知的问题.有几个解决方案:

1)只需将定义直接放在.h文件中,就像之前一样.这有利有弊,包括解决问题(专业),可能使代码不易阅读,并且在某些编译器上难以调试(con)并且可能增加代码膨胀(con).

2)将实现放在x.cpp中,并#include "x.cpp"从内部开始x.h.如果这看起来很时髦又错误,请记住,#include除了读取指定文件并编译它之外,就好像该文件是其中的一部分.x.cpp 换句话说,这确实解决了#1上面的解决方案,但它使它们保持独立物理文件.在做这种事情时,你不要试图自己编译#included文件是至关重要的.出于这个原因,我通常会给这些类型的文件一个hpp扩展名,以区别于h文件和cpp文件.

文件:dumper2.h

#include <iostream>
#include <string>
#include <vector>

void test();
template <class T> void dumpVector( std::vector<T> v,std::string sep);
#include "dumper2.hpp"
Run Code Online (Sandbox Code Playgroud)

文件:dumper2.hpp

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
  typename std::vector<T>::iterator vi;

  vi = v.begin();
  std::cout << *vi;
  vi++;
  for (;vi<v.end();vi++) {
    std::cout << sep << *vi ;
  }
  std::cout << "\n";
  return;

}
Run Code Online (Sandbox Code Playgroud)

3)由于问题在于dumpVector尝试使用它的翻译单元不知道特定的实例化,因此可以在与定义模板的位置相同的翻译单元中强制对其进行特定实例化.只需将此:template void dumpVector<int>(std::vector<int> v, std::string sep);... 添加 到定义模板的文件中.这样做,你不再需要#includehpp从内文件h的文件:

文件:dumper2.h

#include <iostream>
#include <string>
#include <vector>

void test();
template <class T> void dumpVector( std::vector<T> v,std::string sep);
Run Code Online (Sandbox Code Playgroud)

文件:dumper2.cpp

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
  typename std::vector<T>::iterator vi;

  vi = v.begin();
  std::cout << *vi;
  vi++;
  for (;vi<v.end();vi++) {
    std::cout << sep << *vi ;
  }
  std::cout << "\n";
  return;
}

template void dumpVector<int>(std::vector<int> v, std::string sep);
Run Code Online (Sandbox Code Playgroud)

顺便说一句,总而言之,您的模板功能采用了vector 按值.您可能不想这样做,并通过引用或指针传递它,或者更好的是,传递迭代器以避免临时复制整个向量.


Jer*_*fin 10

这就是export关键字应该完成的事情(即,通过export模板,你可以将它放在源文件而不是标题中.不幸的是,只有一个编译器(Comeau)真正export完全实现.

至于为什么其他编译器(包括gcc)没有实现它,原因很简单:因为export很难正确实现.模板内部的代码可以(几乎)完全改变意义,基于模板实例化的类型,因此您无法生成编译模板结果的常规目标文件.例如,x+y可能编译为本机代码,就像mov eax, x/add eax, y在实例化时一样int,但是如果实例化过度std::string重载,则编译为函数调用operator+.

为了支持单独的模板编译,您必须执行所谓的两阶段名称查找(即,在模板的上下文中以及在实例化模板的上下文中查找名称).您通常还让编译器将模板编译为某种数据库格式,该格式可以在任意类型的集合上保存模板的实例化.然后,您可以在编译和链接之间添加一个阶段(尽管它可以构建到链接器中,如果需要),它可以检查数据库,如果它不包含在所有必需类型上实例化的模板的代码,则重新调用编译器在必要的类型上实例化它.

由于极端努力,缺乏实施等,委员会已投票决定export从下一版本的C++标准中删除.另外两个相当不同的提案(模块和概念)已经提出,每个提议至少提供了一部分export意图,但是以(至少希望)更有用和合理的方式实施.


Jul*_*ain 5

模板参数作为编译时解析.

编译器找到.h,找到dumpVector的匹配定义并存储它.编译完成了这个.h.然后,它继续解析文件和编译文件.当它在.cpp中读取dumpVector实现时,它正在编译一个完全不同的单元.没有什么可以尝试在dumper2.cpp中实例化模板,因此简单地跳过了模板代码.编译器不会尝试模板的所有可能类型,希望以后链接器会有一些有用的东西.

然后,在链接时,没有编译int类型的dumpVector实现,因此链接器将找不到任何实现.因此,为什么你会看到这个错误.

出口关键字被设计来解决这个问题,遗憾的是很少的编译器支持.因此,请使用与定义相同的文件来保持实现.