链接错误:重复的符号

3

我有以下4个源文件:

//a.h

#pragma once

namespace proj {
class A {} a;
} // namespace proj

//b.h

#pragma once

namespace proj {
int foo();
} // namespace proj

// b.cpp

#include "proj/a.h"

namespace proj {
int foo() {
  A b = a;
  return 0;
}
} // namespace proj

// c.cpp

#include "proj/a.h"
#include "proj/b.h"

using namespace proj;

int main() {
  A b = a;
  foo();
  return 0;
}

当我尝试编译c.cpp时,出现以下链接错误:

duplicate symbol proj::a      in:
    buck-out/gen/proj/c#compile-c.cpp.ob5f76e97,default/c.cpp.o
    buck-out/gen/proj/b#default,static/libb.a(b.cpp.o)
duplicate symbol ___odr_asan._ZN4proj1aE in:
    buck-out/gen/proj/c#compile-c.cpp.ob5f76e97,default/c.cpp.o
    buck-out/gen/proj/b#default,static/libb.a(b.cpp.o)
ld: 2 duplicate symbols for architecture x86_64
collect2: error: ld returned 1 exit status

Build failed: Command failed with exit code 1.
stderr: duplicate symbol proj::a      in:
    buck-out/gen/proj/c#compile-c.cpp.ob5f76e97,default/c.cpp.o
    buck-out/gen/proj/b#default,static/libb.a(b.cpp.o)
duplicate symbol ___odr_asan._ZN4proj1aE in:
    buck-out/gen/proj/c#compile-c.cpp.ob5f76e97,default/c.cpp.o
    buck-out/gen/proj/b#default,static/libb.a(b.cpp.o)
ld: 2 duplicate symbols for architecture x86_64
collect2: error: ld returned 1 exit status

我认为这是因为b.cpp与c.cpp分别编译,预处理器在每个文件中单独包含头文件a.h,当链接时,链接器会找到两个版本的符号a。
如何声明一个类(在本例中是a),使其可以在整个程序中使用并避免上述链接错误?
参考资料
我正在使用gcc-7(gcc-7(Homebrew GCC 7.2.0_1)7.2.0)在Mac OS X 10.13.3上,使用-std=c++17
构建系统
这应该与问题无关,但如果有人觉得有用,也可以包括在内。
我正在使用buck编译代码(尽管这应该与问题无关),BUCK文件如下:
cxx_library(
    name='a',
    exported_headers=['a.h'],
    visibility=['PUBLIC'],
)

cxx_library(
    name='b',
    exported_headers=['b.h'],
    srcs = ['b.cpp'],
    deps = [':a'],
    visibility=['PUBLIC'],
)

cxx_binary(
    name='c',
    srcs = ['c.cpp'],
    deps = [':a', ':b'],
)

2
你的 a.h 声明了一个变量 class A {} a;,因此每个包含 a.h 的 cpp 文件都会重新声明 proj::a。你需要在只有一个 cpp 文件中声明它,并将其作为 extern 在 a.h 中声明。 - Richard Critten
1
我们来退一步。你似乎把 a 当作“默认值”。为什么这样做? - StoryTeller - Unslander Monica
所以对于我的“实际”用例,我的类需要进行一些昂贵的设置工作,而对于大多数情况下,我只需要一个类的默认实例化。因此,与其每次重新实例化,我只想要一个在整个程序中都可以访问的实例。 - user1413793
1
但是你把它复制到了所有地方。如果默认构造如此昂贵,那么复制真的很便宜吗? - StoryTeller - Unslander Monica
据我所知(从这个简单的例子:https://godbolt.org/g/RQWwi5),它不应该在所有地方都复制对象。在我给出的代码示例中,是的,我正在复制它,但那只是为了展示用法。在我的实际代码中,我只引用已声明的对象。 - user1413793
2个回答

5

如果标记为C++17,您可以利用新的内联变量语言特性:

namespace proj {
    class A {};
    inline A a;
} // namespace proj

inline变量现在与inline函数的行为相同:多个定义的a将被折叠成一个。


它们会被折叠成一个,还是会被多次实例化(每个编译单元一次)? - Bill Kotsias

1

对于标准之前的解决方案:1

链接器错误的原因非常明显。

//a.h

#pragma once

namespace proj {
    class A {} a; // Declares proj::A proj::a implicitly as an instance
                  // everywhere a.h is included.
                  // Thus the linker gets confused which one to use primarly.
}

方案一(单个实例):

我如何声明一个类的实例(在本例中为a),使其可在整个程序中使用并避免上述链接错误?

// a.h
#pragma once

namespace proj {
    class A {};
    extern A a;
}

// a.cpp
#include "a.h"

namespace proj {
    proj:A a;
}

备选方案2(每个翻译单元一个实例):

// a.h
#pragma once

namespace proj {
    class A {};
}

// b.cpp   
#include "a.h"

namespace { // <<< unnamed (aka anonymous) namespace
            //     privately visible for translation unit
    proj:A a;
}

// c.cpp   
#include "a.h"

namespace {
    proj:A a;
}

1)否则可能@Barry's answer最适用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接