想象一下,我们有三个.h
个文件:
f.h
:
template <typename T> class Class {public: Class() {} T id(T x) { return x; }};
g.h
:
template <typename T> class Class {public: Class() {} T id(T x) { return x + 100; }};
h.h
:
template <typename T> class Class {public: Class(); T id(T x); };
现在,我们还有三个.cpp
个文件:
f.cpp
:
#include "f.h"
int f(int x) { Class<int> t; return t.id(x); }
g.cpp
:
#include "g.h"
int g(int x) { Class<int> t; return t.id(x); }
h.cpp
:
#include "h.h"
int h(int x) { Class<int> t; return t.id(x); }
编译它们会为我们提供f.o
,g.o
和h.o
。现在让我们抛出这个main.cpp
:
#include <stdio>
extern int f(int);
extern int g(int);
extern int h(int);
int main() {
std::cout << f(1) << std::endl;
std::cout << g(2) << std::endl;
std::cout << h(3) << std::endl;
}
A-a-让我们做g++ main.cpp f.o g.o h.o
。现在出乎我的意料:由于这三个.o
文件包含int Class<int>::id(int)
的三个不同定义,我希望得到一个链接错误。但是,我得到的是一个工作a.out
,它打印1 2 3
。如果我在命令中重新排序.o
个文件,它将打印101 102 103
。
现在对于实际问题:链接器在这种情况下如何执行链接?它如何确定要保留的Class<int>
的实例化以及要丢弃的内容?为什么它不抱怨多重定义?
nm
实用程序为nm f.o g.o h.o
提供了以下输出:
f.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 t .text$_ZN5ClassIiE2idEi
00000000 t .text$_ZN5ClassIiEC1Ev
00000000 T __Z1fi
00000000 T __ZN5ClassIiE2idEi
00000000 T __ZN5ClassIiEC1Ev
g.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 t .text$_ZN5ClassIiE2idEi
00000000 t .text$_ZN5ClassIiEC1Ev
00000000 T __Z1gi
00000000 T __ZN5ClassIiE2idEi
00000000 T __ZN5ClassIiEC1Ev
h.o:
00000000 b .bss
00000000 d .data
00000000 d .eh_frame
00000000 t .text
00000000 T __Z1hi
U __ZN5ClassIiE2idEi
U __ZN5ClassIiEC1Ev
显然,f.o
和g.o
导出符号__ZN5ClassIiE2idEi
,h.o
导入此符号(大写字母表示外部链接)。它不会导致任何错误。为什么呢?
答案 0 :(得分:3)
这个问题实际上是众所周知的:你违反了ODR。由于模板的性质,编译器和链接器不会注意到这一点。会发生以下情况:
链接器假定 ODR未被违反,因此可以自由使用模板的任何实例化(也就是说。具有相同参数的模板的所有实例化都会导致完全相同)正在生成的代码)。只要您不违反ODR,此系统就有意义。
现在在你的情况下,链接器选择使用它获得的第一个实例化,这导致结果依赖于链接的顺序。
答案 1 :(得分:0)
听起来太明显了,但你的班级正在使用隐式内联。也就是说,当您将源代码放在类声明中时,您正在“暗示”您希望它内联。 所以......
链接器将看到的唯一内容是f(),g()和h()。
使用类似的东西重新编写代码可能会很有趣,虽然它仍然很简单,编译器可能只是简单地内联它(取决于你的编译器)。
template <typename T> class Class
{
public:
Class() {}
T id(T x);
};
template <typename T>
T Class::id(T x)
{
return x;
}