为什么这个包含顺序会导致 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
中,我在 Bh
之后包含 Ah
code>,程序编译成功。 有人可以解释为什么吗?
我花了很长时间才在实际代码中找到问题,有一些方法可以让我轻松理解该错误与包含顺序有关吗?
编辑: 我做了一些其他测试来调查这个问题。
如果我使用 std::unordered_set
更改 std::unordered_map
但不使用 std::map
更改,也会发生错误。 A,int>
和 std::set
,所以我认为哈希存在一些问题。
正如建议的,在 Bh
中包含 Ah
而不是向前声明 A 可以使构建成功,而无需修改 main.cpp
中的包含顺序。
所以我认为问题变成了:为什么向前声明 A,从而使无序映射的键具有不完整的类型,会导致错误?
I had a problem with include order that I cannot explain. I will show you a minimal example with four files:
// 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();
}
With this example I get the following error:
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
But if in main.cpp
I include A.h
after B.h
, the program compiles successfully.
Can someone explain why?
It took me a long time to find the problem in real code, there is some method to make me understand easily that the error is related to include order?
Edit:
I made some other test to investigate the problem.
The error occurs also if I change the std::unordered_map<A, int>
with std::unordered_set<A>
but not with std::map<A, int>
and std::set<A>
, so I think that there is some problem with the hash.
As suggested, including A.h
in B.h
instead of forward declaring A makes the build succeed without modifying the include order in main.cpp
.
So I think that the question become: why forward declaring A, and thus having an incomplete type for the key of unordered map, causes the error?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我在 Visual Studio 2022 中测试了相同的代码并得到了相同的错误。经过我的摸索,我发现了问题所在。
首先,我将 Ah 和 Bh 的内容复制到 main.cpp 中,并删除了
#include
指令。编译后,我仍然遇到同样的错误。然后我测试发现,只要将
namespace std {...}
移动到类B
的定义之后,错误就消失了。我看了编译器生成的汇编代码,发现main.cpp和b.cpp中为
GetMap
生成的名称不同:main.asm:
b.asm:
我查了 MSVC 的 名称修改规则并找到
struct 的
和U
V
代表class
。所以我改变了template<> 的定义类哈希
到template<>结构散列
。然后错误就消失了。
我认为在专业化中使用
class
关键字是合法的,但我在标准中找不到对此的描述。不过,我想问题可能没那么简单。这里的一个关键问题是,B.cpp 中
std::hash
的特化出现在类B
的定义之后,而在 main.cpp 中,顺序完全相反。我认为这违反了 ODR 并且应该导致未定义的行为。这也是为什么交换头文件的顺序(使其与B.cpp中的顺序一致)后程序是正确的。我查了一些资料,找不到问题的标准描述:在B.cpp中,
GetMap
的声明不会导致unordered_map
被实例化,但它在函数定义中实例化。在声明和定义中间插入了std::hash
的特化,这可能导致unordered_map
看到std::hash
的不同定义代码>.编译器可以看到这个特化吗?编译器应该选择这个专业化吗?如果编译器可以看到专门化,为什么在生成的汇编代码中使用主模板? (B.cpp 中编译器生成的名称使用“U”,代表struct
)I tested the same code in Visual Studio 2022 and got the same error. After my exploration, I found the problem.
Firstly, I copied the contents of A.h and B.h into main.cpp and removed the
#include
directive. After compiling, I still got the same error.Then I tested and found that as soon as I moved
namespace std {...}
after the definition of classB
, the error went away.I read the assembly code generated by the compiler and found that the names generated for
GetMap
in main.cpp and b.cpp are different:main.asm:
b.asm:
I looked up MSVC's name mangling rules and found
U
forstruct
andV
forclass
. So I change the definition oftemplate<> class hash<A>
totemplate<> struct hash<A>
. Then the error disappeared.I think it's legal to use
class
keyword instead in the specialization, but I can't find a description of this in the standard.However, I think the problem may not be that simple. A key issue here is that the specialization of
std::hash
in B.cpp appears after the definition of classB
, while in main.cpp the order is completely reversed. I think this violates the ODR and should result in undefined behavior. This is also why the program is correct after swapping the order of the header files (making it consistent with the order in B.cpp).I looked up some sources and couldn't find a standard description of the problem: In B.cpp, the declaration of
GetMap
does not causeunordered_map
to be instantiated, but it is instantiated in the function definition. A specialization ofstd::hash
was inserted in the middle of declaration and definition, which could causeunordered_map
to see a different definition ofstd::hash
. Can the compiler see this specialization? Should the compiler choose this specialization? If the compiler can see the specialization, why is the primary template used in the generated assembly code? (The compiler-generated name in B.cpp uses "U", which is forstruct
)