我有一个基类,它有一个由两个类实现的纯虚方法:
// base_class.hpp
class base_class {
public:
virtual std::string hello() = 0;
};
// base_implementer_1.hpp
class base_implementer1 : base_class {
public:
std::string hello();
};
// base_implementer_2.hpp
class base_implementer2 : base_class {
public:
std::string hello();
};
// base_implementer_1.cpp
std::string hello() {
return(std::string("Hello!"));
}
// base_implementer_2.cpp
std::string hello() {
return(std::string("Hola!"));
}
请注意实现中缺少base_implementer1::
和base_implementer2::
。这是故意的。
通过添加base_implementer1::
和base_implementer2::
,我没有收到多重定义错误。然而,让他们离开链接器抱怨我有两个相同功能的定义(hello()
)。
由于头文件中没有这两个实现,我认为(即使它们在实施hello()
方面不正确)也会被允许,因为没有理由你不能拥有它们两个hello()
文件中的两个.cpp
函数。但事实似乎并非如此。谁能告诉我链接器中发生了什么事情才能发生这种多重定义错误?
答案 0 :(得分:2)
One-Definition-Rule定义了两个范围的规则,即翻译单元范围和程序范围。
以下具有翻译单位范围的规则规定,相同的翻译单元不得包含同一功能的两个不同定义:
任何变量,函数,类类型,枚举只有一个定义 任何一个翻译单元允许使用类型或模板 这些可能有多个声明,但只有一个定义 允许的)。
因此,如果您有两个不同的.cpp文件,那么您有两个不同的翻译单元,并且每个翻译单元都有自己的hello()
定义;在翻译单位的范围内不违反ODR。
以下带有程序范围的规则定义了一个odr使用的函数必须在程序中只定义一次:
每个非内联函数或变量的唯一定义 在整个过程中需要使用odr-used(见下文) 程序(包括任何标准和用户定义的库)。该 编译器不需要诊断此违规,但行为 违反它的程序是未定义的。
odr-used的定义非正式地指出,对于每个被调用的函数或者哪个地址都必须在程序中定义:
非正式地说,如果一个对象的地址被采用,或者一个对象被使用了 引用绑定到它,如果函数是函数,则使用它 打电话给它或其地址。如果一个对象或一个 函数是odr-used,它的定义必须存在于某个地方 程序;违反这一点的是链接时错误。
因此,如果多个.cpp文件公开hello()
的实现,并且如果调用或引用此函数,则显然违反了程序范围内的ODR。
如果相应的功能没有使用(即被调用或引用),ODR应该 - 据我的理解 - 不会被违反;
如果编译器抱怨重复的符号,那么这是因为该程序违反了链接规则(请同时授予SO answer关于“如果我不使用变量”的话。 C ++11§3.5[basic.link] / 9州:
两个相同且在不同范围内声明的名称 应表示相同的变量,函数,类型,枚举器,模板 或命名空间
- 两个名称都有外部链接,或者两个名称都有内部链接,并在同一个翻译单元中声明;和......
要避免这种情况,请确保最多公开hello()
的一个实现,并使所有其他实现static
或使用未命名的命名空间。
在C编程语言中,static
与全局变量和函数一起使用,以将其范围设置为包含文件,即它不公开此实现,并且避免与其他二进制文件发生冲突。
因此,合理的建议是:制作仅在翻译单元中使用的功能定义,仅对此翻译单元可见;并定义在名称空间或类中公开的函数,以避免链接器中出现意外或不可预见的名称冲突/重复符号问题。
答案 1 :(得分:0)
在base_implementor_1.cpp中定义一个名为hello
的全局函数。您在base_implementor_2.cpp中定义了另一个名为hello
的全局函数。这导致违反ODR的多重定义和所需错误。为什么这是个问题?如果你有一个调用hello()
的第三个源文件,应该调用哪个函数?
如果要在多个源文件中定义具有相同名称的不同功能,可以在前面加上static
关键字
static void hello() { }
或在匿名命名空间内
namespace {
void hello() { }
}
答案 2 :(得分:0)
在两个不同的翻译单元中,您对名为hello
的函数有两种不同的定义。当涉及链接时,链接器不知道要链接到哪个hello
函数。
考虑:
A.cpp
#include <string>
std::string hello() {
return "A";
}
B.cpp
#include <string>
std::string hello() {
return "B";
}
C.cpp
#include <iostream>
std::string hello();
int main() {
std::cout << hello() << '\n';
}
链接器怎么可能知道hello
中要调用哪个main
?它不能,因为违反了一个定义规则。