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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3040480/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 11:52:55  来源:igfitidea点击:

C++ template function compiles in header but not implementation

c++templatesstlvectorg++

提问by flies

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):

我正在尝试学习模板,但遇到了这个令人困惑的错误。我在头文件中声明了一些函数,我想制作一个单独的实现文件,在其中定义函数。这是调用标头 (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);
}

Now, here's a working header file (dumper2.h):

现在,这是一个可用的头文件 (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;
}

With implementation (dumper2.cpp):

随着实现(dumper2.cpp):

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

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

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.

奇怪的是,如果我将定义 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

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

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

回答by John Dibling

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:

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

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).

1) 像之前一样,将定义直接放在 .h 文件中。这有利有弊,包括解决问题 (pro),可能使代码的可读性降低,并且在某些编译器上更难调试 (con),并且可能会增加代码膨胀 (con)。

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 #includedoes nothing more than read the specified file and compile it as if that file were part of x.cppIn 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 hppextension to distinguish them from hfiles and from cppfiles.

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

File: dumper2.h

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

File: 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 << "\n";
  return;

}

3) Since the problem is that a particular instantiation of dumpVectoris 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 #includethe hppfile from within the hfile:

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

File: dumper2.h

文件:dumper2.h

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

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

File: dumper2.cpp

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

By the way, and as a total aside, your template function is taking a vectorby-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.

顺便说一句,总而言之,您的模板函数正在接受一个vectorby-value。您可能不想这样做,而是通过引用或指针传递它,或者更好的是,传递迭代器以避免创建临时和复制整个向量。

回答by Jerry Coffin

This was what the exportkeyword was supposed to accomplish (i.e., by exporting the template, you'd be able to put it in a source file instead of a header. Unfortunately, only one compiler (Comeau) ever really implemented exportcompletely.

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

As to why the other compilers (including gcc) didn't implement it, the reason is pretty simple: because exportis extremely difficult to implement correctly. Code insidethe template can change meaning (almost) completely, based on the type over which the template is instantiated, so you can't generate a conventional object file of the result of compiling the template. Just for example, x+ymight compile to native code like mov eax, x/add eax, ywhen instantiated over an int, but compile to a function call if instantiated over something like std::stringthat overloads operator+.

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

To support separate compilation of templates, you have to do what's called two-phase name lookup (i.e., lookup the name both in the context of the template andin the context where the template is being instantiated). You typically also have the compiler compile the template to some sort of database format that can hold instantiations of the template over an arbitrary collection of types. You then add in a stage between compiling and linking (though it can be built into the linker, if desired) that checks the database and if it doesn't contain code for the template instantiated over all the necessary types, re-invokes the compiler to instantiate it over the necessary types.

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

Due to the extreme effort, lack of implementation, etc., the committee has voted to remove exportfrom the next version of the C++ standard. Two other, rather different, proposals (modules and concepts) have been made that would each provide at least part of what exportwas intended to do, but in ways that are (at least hoped to be) more useful and reasonable to implement.

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

回答by Julien Lebosquain

Template parameters are resolved as compile time.

模板参数被解析为编译时。

The compiler finds the .h, finds a matching definition for dumpVector, and stores it. The compiling is finished for this .h. Then, it continues parsing files and compiling files. When it reads the dumpVector implementation in the .cpp, it's compiling a totally different unit. Nothing is trying to instantiate the template in dumper2.cpp, so the template code is simply skipped. The compiler won't try every possible type for the template, hoping there will be something useful later for the linker.

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

Then, at link time, no implementation of dumpVector for the type int has been compiled, so the linker won't find any. Hence why you're seeing this error.

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

The exportkeyword is designed to solve this problem, unfortunately few compilers support it. So keep your implementation with the same file as your definition.

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

回答by Tim Kay

A template function is not real function. The compiler turns a template function into a real function when it encounters a use of that function. So the entire template declaration has to be in scope it finds the call to DumpVector, otherwise it can't generate the real function.
Amazingly, a lot of C++ intro books get this wrong.

模板函数不是真正的函数。编译器在遇到该函数的使用时会将模板函数转换为真正的函数。所以整个模板声明必须在它找到对 的调用的范围内DumpVector,否则它不能生成真正的函数。
令人惊讶的是,很多 C++ 介绍书籍都弄错了。

回答by Mark B

This is exactly how templates work in C++, you must put the implementation in the header.

这正是模板在 C++ 中的工作方式,您必须将实现放在标题中。

When you declare/define a template function, the compiler can't magically know which specific types you may wish to use the template with, so it can't generate code to put into a .o file like it could with a normal function. Instead, it relies on generating a specific instantiation for a type when it sees the use of that instantiation.

当您声明/定义模板函数时,编译器无法神奇地知道您可能希望将模板用于哪些特定类型,因此它无法像使用普通函数那样生成要放入 .o 文件的代码。相反,当它看到该实例化的使用时,它依赖于为一个类型生成一个特定的实例化。

So when the implementation is in the .C file, the compiler basically says "hey, there are no users of this template, don't generate any code". When the template is in the header, the compiler is able to see the use in main and actually generate the appropriate template code.

所以当实现在 .C 文件中时,编译器基本上会说“嘿,这个模板没有用户,不要生成任何代码”。当模板在头中时,编译器能够看到 main 中的使用并实际生成适当的模板代码。

回答by Cogwheel

Most compilers don't allow you to put template function definitions in a separate source file, even though this is technically allowed by the standard.

大多数编译器不允许您将模板函数定义放在单独的源文件中,即使标准在技术上允许这样做。

See also:

也可以看看:

http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12

http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12

http://www.parashift.com/c++-faq-lite/templates.html#faq-35.14

http://www.parashift.com/c++-faq-lite/templates.html#faq-35.14