C ++模板函数在标头中编译但未在实现中编译

2022-01-23 00:00:00 templates g++ vector c++ stl

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

I'm trying to learn templates and I've run into this confounding error. I'm declaring some functions in a header file and I want to make a separate implementation file where the functions will be defined. Here's the code that calls the header (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);
}

现在,这是一个有效的头文件 (dumper2.h):

Now, here's a working header file (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 << "
";
    return;
}

有实现(dumper2.cpp):

With implementation (dumper2.cpp):

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

void test() {
    std::cout << "!olleh dlrow
";
}

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

The weird thing is that if I move the code that defines dumpVector from the .h to the .cpp file, I get the following error.

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

那么为什么它以一种方式起作用而不是另一种呢?明明编译器能找到test(),那为什么找不到dumpVector呢?

So why does it work one way and not the other? Clearly the compiler can find test(), so why can't it find dumpVector?

推荐答案

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

The problem you're having is that the compiler doesn't know which versions of your template to instantiate. When you move the implementation of your function to x.cpp it is in a different translation unit from main.cpp, and main.cpp can't link to a particular instantiation because it doesn't exist in that context. This is a well-known issue with C++ templates. There are a few solutions:

1) 只需将定义直接放入 .h 文件中,就像您之前所做的那样.这有优点&缺点,包括解决问题(专业),可能使代码的可读性降低在一些更难调试的编译器上 (con) 并且可能会增加代码膨胀 (con).

1) Just put the definitions directly in the .h file, as you were doing before. This has pros & cons, including solving the problem (pro), possibly making the code less readable & on some compilers harder to debug (con) and maybe increasing code bloat (con).

2) 将实现放在 x.cpp 中,#include "x.cpp"x.h 中.如果这看起来很奇怪和错误,请记住 #include 只是读取指定的文件并编译它就好像该文件是 x.cpp 换句话说,这与上面的解决方案#1 完全相同,但它将它们保存在单独的物理文件中.在做这种事情时,不要尝试自己编译 #included 文件,这一点很重要.出于这个原因,我通常给这些类型的文件一个 hpp 扩展名,以将它们与 h 文件和 cpp 文件区分开来.

2) Put the implementation in x.cpp, and #include "x.cpp" from within x.h. If this seems funky and wrong, just keep in mind that #include does nothing more than read the specified file and compile it as if that file were part of x.cpp In other words, this does exactly what solution #1 does above, but it keeps them in seperate physical files. When doing this kind of thing, it is critical that you not try to compile the #included file on it's own. For this reason, I usually give these kinds of files an hpp extension to distinguish them from h files and from cpp files.

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

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

文件: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 << "
";
  return;

}

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

3) Since the problem is that a particular instantiation of dumpVector is not known to the translation unit that is trying to use it, you can force a specific instantiation of it in the same translation unit as where the template is defined. Simply by adding this: template void dumpVector<int>(std::vector<int> v, std::string sep); ... to the file where the template is defined. Doing this, you no longer have to #include the hpp file from within the h file:

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

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

文件: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 << "
";
  return;
}

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

顺便说一句,总的来说,您的模板函数采用 vector by-value.您可能不想这样做,并通过引用或指针传递它,或者更好的是,传递迭代器以避免产生临时 &复制整个向量.

By the way, and as a total aside, your template function is taking a vector by-value. You may not want to do this, and pass it by reference or pointer or, better yet, pass iterators instead to avoid making a temporary & copying the whole vector.

相关文章