为什么这个包含顺序会导致 unordered_map 上的链接错误?

发布于 2025-01-09 01:59:07 字数 1924 浏览 1 评论 0原文

我遇到了无法解释的包含顺序问题。我将向您展示一个包含四个文件的最小示例:

// 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

零時差 2025-01-16 01:59:07

我在 Visual Studio 2022 中测试了相同的代码并得到了相同的错误。经过我的摸索,我发现了问题所在。

首先,我将 Ah 和 Bh 的内容复制到 main.cpp 中,并删除了 #include 指令。编译后,我仍然遇到同样的错误。

然后我测试发现,只要将 namespace std {...} 移动到类 B 的定义之后,错误就消失了。

我看了编译器生成的汇编代码,发现main.cpp和b.cpp中为GetMap生成的名称不同:

main.asm:

GetMap@B@@QEBAAEBV?$unordered_map@UA@@HV?$hash@UA@@@std@@U?$equal_to@UA@@@3@V?$分配器@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?$分配器@U?$pair@$$CBUA@@H@std@@@3@@std@@XZ

我查了 MSVC 的 名称修改规则并找到 struct 的 UV 代表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 class B, 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:

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

I looked up MSVC's name mangling rules and found U for struct and V for class. So I change the definition of template<> class hash<A> to template<> 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 class B, 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 cause unordered_map to be instantiated, but it is instantiated in the function definition. A specialization of std::hash was inserted in the middle of declaration and definition, which could cause unordered_map to see a different definition of std::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 for struct)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文