我希望将一些包装器代码作为仅包含 header 的库。我受到 boost 库的启发,只保留它们的标题,以便简化分发 .lib 和包括编译 .cpp 的需要。
在此示例中,“z.h”是包装器,a.cpp 已重构为将 B() 移动到它自己的源文件中。现在它不起作用。
z.h
class Z
{
public:
void Foo(); // edited to match my code
};
Z::Foo() { }
a.cpp
#include "z.h"
void A() {
Z z;
z.Foo();
}
//void B(Z z) {
// z.Foo();
//}
b.cpp
#include "z.h"
void B(Z z) {
z.Foo();
}
*error LNK2005: "public: __cdecl void z::Foo()"已经在 b.obj 中定义*
我知道我可以通过将 z.h 分成用于声明的 z.h 和用于定义的 z.cpp 来解决这个问题。
- 但是如果没有 .cpp 文件,Boost 库怎么能成功呢?
- 是否所有内容都需要模板?
- 什么代码可以放在 z.h 中?
最佳答案
暂时考虑一下这个标题:
// foo.hpp
void foo(int x)
{
/* do something */
}
(标题守卫在这里不相关,它们按翻译单元工作。)我们现在有两个翻译单元:
// a.cpp
#include "foo.hpp"
和:
// b.cpp
#include "foo.hpp"
这最终所做的是定义 foo(int x)
两次,每个 TU 一次。根据单一定义规则 (ODR),不允许多重定义,虽然从技术上讲不需要诊断,但对编译器部分来说这样做很简单,因此你会得到错误。
幸运的是,有一个关键字 inline
改变了这种行为:
// foo.hpp
inline void foo(int x)
{
/* do something */
}
这个关键字告诉链接器,如果它遇到多个定义,它可以自由选择一个定义并丢弃其余的。 (由您来确保这实际上没问题!)通过此更改,解决了之前的 ODR 违规问题并且程序可以编译。
现在,您已将 header 列为:
class Z
{
public:
void Foo(){
//do stuff
}
};
这等同于:
class Z
{
public:
void Foo();
};
inline void Z::Foo(){
//do stuff
}
因为在类中定义的函数是隐式内联
。 (这允许您将定义包含在多个翻译单元中而不会出错。)我怀疑您写的不是您帖子中的内容,而是类似这样的内容:
class Z
{
public:
void Foo();
};
void Z::Foo(){
//do stuff
}
inline
缺失。等价性丢失,创建了多个定义,并且您违反了 ODR。
解决方案是使用inline
,或者通过定义类定义中的函数隐式使用它。就个人而言,我发现后者更清晰(因为你避免重复自己),并且更容易维护。 Boost 经常使用 inline
来避免对单一定义的需要。
关于c++ - 重构导致链接器出现重复问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11977601/