我正在尝试创建专门用于某些给定类型的全局函数模板。看起来像这样:
A.h (主要模板,模板专业化,外部)
template <typename T> void foo() { std::cout << "default stuff" << std::endl; }
template<> void foo<int>() { std::cout << "int stuff" << std::endl; }
extern template void foo<int>();
A.cpp (显式实例化)
template void foo<int>();
B.h
void bar();
B.cpp (包括A.h)
void bar() { foo<int>(); }
main.cpp
foo<int>();
bar();
编译器崩溃了:“'void foo()'的多个定义。我认为应该由extern来处理。B编译单元不应实例化foo,而应在链接上使用A实例化时间,不?我在这里弄错了什么?
请注意,如果我不专门研究foo,则代码可以正常编译。函数专门化和实例化之间是否存在某种冲突?
答案 0 :(得分:2)
这里不需要extern
来抑制实例化。通过声明显式专门化,您已经在告诉任何调用foo<int>
的代码以使用显式专门化而不是主要模板。相反,您只想在A.h中声明专业化,然后在A.cpp中定义它:
// A.h
template <typename T> void foo() { std::cout << "default stuff" << std::endl; }
template <> void foo<int>();
// A.cpp
template <> void foo<int>() { std::cout << "int stuff" << std::endl; }
如果您想在某个翻译单元中提供主模板的显式实例化,而不是的显式专业化,则可以使用extern
。
答案 1 :(得分:2)
由于Brian的回答为您提供了一个可行的解决方案,并解释了您为什么不需要外部的原因,我将详细说明您的情况。
首先,您说您的编译器崩溃了,因此您以为这是编译器错误。这不是实际情况。 A.cpp
,B.cpp
和main.cpp
照原样运行,都可以成功编译。没有编译错误。在Visual Studio 2017 CE中,直到我尝试构建程序后才崩溃。这是我的IDE的构建错误。
1>------ Build started: Project: Temp, Configuration: Debug Win32 ------
1>B.obj : error LNK2005: "void __cdecl foo<int>(void)" (??$foo@H@@YAXXZ) already defined in A.obj
1>main.obj : error LNK2005: "void __cdecl foo<int>(void)" (??$foo@H@@YAXXZ) already defined in A.obj
1>C:\Users\...\Temp.exe : fatal error LNK1169: one or more multiply defined symbols found
1>Done building project "Temp.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
这里显示的是Linking
错误。由于定义了多个符号,因此无法链接。
让我们看一下您的代码:
A.h
#pragma once
#include <iostream>
// Not just a declaration of void foo<T>() but also a definition!
template<typename T> void foo() { std::cout << "default stuff\n"; }
// Not just a declaration of void foo<int>() but also a specialized definition!
template<> void foo<int>() { std::cout << "int stuff\n"; }
// external explicit instantiation
extern template void foo<int>();
A.cpp
#include "A.h"
// explicit instantiation
template void foo<int>();
B.h
#pragma once
void bar();
B.cpp
#include "B.h"
#include "A.h"
void bar() {
// instantiation to call foo
foo<int>();
}
main.cpp
#include "A.h"
#include "B.h"
int main() {
// instantiation to call foo
foo<int>();
bar();
return 0;
}
这里发生的是全部3个正在编译,但是当链接器通过传递三个目标文件来构建单个可执行文件时,它将失败。编译器仅检查语言语法-语法并将其转换为目标文件。链接器从编译器接收目标文件,并为变量,函数,类等创建所有所需的符号。
它在main中出现,并且看到#include "A.h"
和#include "B.h"
,因此预编译器已经进行了文本替换并将A.h
和B.h
粘贴在页面顶部,因此,A.h
和B.h
中属于A.cpp
和B.cpp
翻译单位的所有代码现在也都位于main.cpp
翻译单位中。因此,它看到了foo
模板对象,并且碰巧看到了多个定义!它与您使用extern无关。为了说明这一点,我可以删除一些代码,并且由于多个定义而导致链接失败,仍然会生成相同的构建错误。
A.h
#pragma once
#include <iostream>
template<typename T> void foo() { std::cout << "default stuff\n"; }
template<> void foo<int>() { std::cout << "int stuff\n"; }
A.cpp
#include "A.h"
main.cpp
#include "A.h"
int main() {
foo<int>();
return 0
}
给出基本相同的构建链接错误:
1>------ Build started: Project: Temp, Configuration: Debug Win32 ------
1>main.cpp
1>main.obj : error LNK2005: "void __cdecl foo<int>(void)" (??$foo@H@@YAXXZ) already defined in A.obj
1>C:\Users\...\Temp.exe : fatal error LNK1169: one or more multiply defined symbols found
1>Done building project "Temp.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
两者之间的唯一区别是,在A
和B
中找到了多重定义,其中在第二个实例中甚至没有使用B
时都发现了多重定义{{1 }}。
要解决此问题,请使用A
答案!基本上,除非您的定义是在特定的类或名称空间中,而不是在全局范围内,否则通常的规则是将{{1}中的Brian's
和declarations
中的headers
。