为什么这包含订单会导致 unordered_map 上的 link 错误?
Why this include order causes link error on unordered_map?
我无法解释包含顺序的问题。我将向您展示一个包含四个文件的最小示例:
// A.h
#pragma once
#include <functional>
struct A {};
namespace std {
template<>
class hash<A> {
public:
size_t operator()(const A&) const {
return 0;
};
};
}
// B.h
#pragma once
#include <unordered_map>
struct A;
struct B {
const std::unordered_map<A, int>& GetMap() const;
};
// B.cpp
#include "B.h"
#include "A.h"
const std::unordered_map<A, int>& B::GetMap() const {
static std::unordered_map<A, int> m;
return m;
}
// main.cpp
#include "A.h" // To be included AFTER B.h
#include "B.h"
int main() {
B b{};
const auto& m = b.GetMap();
}
在这个例子中我得到以下错误:
error LNK2019: unresolved external symbol "public: class std::unordered_map<struct A,int,class std::hash<struct A>,struct std::equal_to<struct A>,class std::allocator<struct std::pair<struct A const ,int> > > const & __cdecl B::GetMap(void)const " (?GetMap@B@@QEBAAEBV?$unordered_map@UA@@HV?$hash@UA@@@std@@U?$equal_to@UA@@@3@V?$allocator@U?$pair@$$CBUA@@H@std@@@3@@std@@XZ) referenced in function main
1>Z:\Shared\sources\Playground\x64\Debug\Playground.exe : fatal error LNK1120: 1 unresolved externals
但是如果在 main.cpp
中我在 B.h
之后包含 A.h
,程序编译成功。
有人可以解释为什么吗?
找了很久才在实际代码中找到问题,有什么方法可以让我很容易理解这个错误与include order有关吗?
编辑:
我做了一些其他测试来调查这个问题。
如果我将 std::unordered_map<A, int>
更改为 std::unordered_set<A>
而不是 std::map<A, int>
和 std::set<A>
,也会出现此错误,因此我认为散列存在一些问题.
如建议的那样,在 B.h
中包含 A.h
而不是向前声明 A 使得构建成功而无需修改 main.cpp
中的包含顺序。
所以我认为问题变成了:为什么前向声明 A 并因此具有无序映射的键的不完整类型会导致错误?
我在 Visual Studio 2022 中测试了相同的代码并得到了相同的错误。经过我的探索,我发现了问题。
首先,我将 A.h 和 B.h 的内容复制到 main.cpp 中,并删除了 #include
指令。编译后还是报同样的错误
然后测试发现在classB
的定义后面一移动namespace std {...}
,错误就消失了
我看了编译器生成的汇编代码,发现GetMap
在main.cpp和b.cpp中生成的名字是不一样的:
main.asm:
GetMap@B@@QEBAAEBV?$unordered_map@UA@@HV?$hash@UA@@@std@@U?$equal_to@UA@@@3@V?$allocator@U?$pair@$$CBUA@@H@std@@@3@@std@@XZ
b.asm:
GetMap@B@@QEBAAEBV?$unordered_map@UA@@HU?$hash@UA@@@std@@U?$equal_to@UA@@@3@V?$allocator@U?$pair@$$CBUA@@H@std@@@3@@std@@XZ
我查找了 MSVC 的 name mangling rules,发现 U
对应 struct
,V
对应 class
。所以我把template<> class hash<A>
的定义改成了template<> struct hash<A>
。然后错误就消失了。
我认为在专业化中使用 class
关键字是合法的,但我在标准中找不到对此的描述。
不过,我觉得问题可能没那么简单。这里的一个关键问题是 B.cpp 中 std::hash
的特化出现在 class B
的定义之后,而 main.cpp 中的顺序完全颠倒了。我认为这违反了 ODR,应该会导致未定义的行为。这也是为什么调换头文件顺序后程序正确的原因(使其与B.cpp中的顺序一致)。
我查了一些资料,找不到问题的标准描述:在B.cpp中,GetMap
的声明不会导致unordered_map
被实例化,但是它在函数定义中实例化。在声明和定义中间插入了 std::hash
的特化,这可能导致 unordered_map
看到 std::hash
的不同定义。编译器能看到这种特化吗?编译器应该选择这个专业吗?如果编译器可以看到特化,为什么在生成的汇编代码中使用主模板? (B.cpp中的compiler-generated名称使用“U”,代表struct
)
我无法解释包含顺序的问题。我将向您展示一个包含四个文件的最小示例:
// A.h
#pragma once
#include <functional>
struct A {};
namespace std {
template<>
class hash<A> {
public:
size_t operator()(const A&) const {
return 0;
};
};
}
// B.h
#pragma once
#include <unordered_map>
struct A;
struct B {
const std::unordered_map<A, int>& GetMap() const;
};
// B.cpp
#include "B.h"
#include "A.h"
const std::unordered_map<A, int>& B::GetMap() const {
static std::unordered_map<A, int> m;
return m;
}
// main.cpp
#include "A.h" // To be included AFTER B.h
#include "B.h"
int main() {
B b{};
const auto& m = b.GetMap();
}
在这个例子中我得到以下错误:
error LNK2019: unresolved external symbol "public: class std::unordered_map<struct A,int,class std::hash<struct A>,struct std::equal_to<struct A>,class std::allocator<struct std::pair<struct A const ,int> > > const & __cdecl B::GetMap(void)const " (?GetMap@B@@QEBAAEBV?$unordered_map@UA@@HV?$hash@UA@@@std@@U?$equal_to@UA@@@3@V?$allocator@U?$pair@$$CBUA@@H@std@@@3@@std@@XZ) referenced in function main
1>Z:\Shared\sources\Playground\x64\Debug\Playground.exe : fatal error LNK1120: 1 unresolved externals
但是如果在 main.cpp
中我在 B.h
之后包含 A.h
,程序编译成功。
有人可以解释为什么吗?
找了很久才在实际代码中找到问题,有什么方法可以让我很容易理解这个错误与include order有关吗?
编辑: 我做了一些其他测试来调查这个问题。
如果我将 std::unordered_map<A, int>
更改为 std::unordered_set<A>
而不是 std::map<A, int>
和 std::set<A>
,也会出现此错误,因此我认为散列存在一些问题.
如建议的那样,在 B.h
中包含 A.h
而不是向前声明 A 使得构建成功而无需修改 main.cpp
中的包含顺序。
所以我认为问题变成了:为什么前向声明 A 并因此具有无序映射的键的不完整类型会导致错误?
我在 Visual Studio 2022 中测试了相同的代码并得到了相同的错误。经过我的探索,我发现了问题。
首先,我将 A.h 和 B.h 的内容复制到 main.cpp 中,并删除了 #include
指令。编译后还是报同样的错误
然后测试发现在classB
的定义后面一移动namespace std {...}
,错误就消失了
我看了编译器生成的汇编代码,发现GetMap
在main.cpp和b.cpp中生成的名字是不一样的:
main.asm:
GetMap@B@@QEBAAEBV?$unordered_map@UA@@HV?$hash@UA@@@std@@U?$equal_to@UA@@@3@V?$allocator@U?$pair@$$CBUA@@H@std@@@3@@std@@XZ
b.asm:
GetMap@B@@QEBAAEBV?$unordered_map@UA@@HU?$hash@UA@@@std@@U?$equal_to@UA@@@3@V?$allocator@U?$pair@$$CBUA@@H@std@@@3@@std@@XZ
我查找了 MSVC 的 name mangling rules,发现 U
对应 struct
,V
对应 class
。所以我把template<> class hash<A>
的定义改成了template<> struct hash<A>
。然后错误就消失了。
我认为在专业化中使用 class
关键字是合法的,但我在标准中找不到对此的描述。
不过,我觉得问题可能没那么简单。这里的一个关键问题是 B.cpp 中 std::hash
的特化出现在 class B
的定义之后,而 main.cpp 中的顺序完全颠倒了。我认为这违反了 ODR,应该会导致未定义的行为。这也是为什么调换头文件顺序后程序正确的原因(使其与B.cpp中的顺序一致)。
我查了一些资料,找不到问题的标准描述:在B.cpp中,GetMap
的声明不会导致unordered_map
被实例化,但是它在函数定义中实例化。在声明和定义中间插入了 std::hash
的特化,这可能导致 unordered_map
看到 std::hash
的不同定义。编译器能看到这种特化吗?编译器应该选择这个专业吗?如果编译器可以看到特化,为什么在生成的汇编代码中使用主模板? (B.cpp中的compiler-generated名称使用“U”,代表struct
)