我有一个基类,它有一个由两个类实现的纯虚拟方法:
// 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()
方面不正确)它们是允许的,因为没有理由不能在两个不同的.cpp
文件中拥有两个hello()
函数。但事实似乎并非如此。有人能告诉我链接器中发生了什么导致了这个多重定义错误吗?
One Definition Rule定义了两个作用域的规则,即翻译单元作用域和程序作用域。
以下具有翻译单元范围的规则规定,同一翻译单元不得包含同一功能的两个不同定义:
任何变量、函数、类类型、枚举只有一个定义在任何一个翻译单元中都允许使用类型或模板(这些可能有多个声明,但只有一个定义允许)。
因此,如果您有两个不同的.cpp文件,则您有两种不同的翻译单元,并且它们中的每一个都可能有自己的hello()
定义;在翻译单位的范围内不违反ODR。
以下具有程序范围的规则定义了odr使用的函数必须在程序中定义一次:
每个非内联函数或变量的唯一定义使用的odr(见下文)需要出现在整个程序(包括任何标准库和用户定义库)。这个编译器不需要诊断此冲突,但需要诊断行为违反它的程序的是未定义的。
非正式使用的odr的定义指出,对于每个被调用或地址被占用的函数,都必须在程序中定义:
在非正式情况下,如果一个对象的地址被占用,或者引用绑定到它,如果函数对其进行调用或获取其地址。如果对象或函数是odr使用的,其定义必须存在于程序违反这一点就是链接时间错误。
因此,如果多个.cpp文件公开了hello()
的实现,并且如果调用或引用了此函数,则显然违反了程序范围内的ODR。
如果未使用(即调用或引用)相应的函数,则在我看来,不应违反odr;
如果编译器抱怨符号重复,那是因为程序违反了链接规则(请同时给出关于"如果我不使用变量"的SO答案)。C++11§3.5[基本链接]/9状态:
两个相同的名称在不同的作用域中声明应表示相同的变量、函数、类型、枚举器、模板或命名空间(如果)
- 两个名称都具有外部链接,或者两个名称具有内部链接并在同一翻译单元中声明;而且
要避免这种情况,请确保最多公开一个hello()
的实现,并将所有其他实现设为static
或使用未命名的命名空间。
在C编程语言中,static
与全局变量和函数一起使用,将它们的作用域设置为包含文件,即它不公开此实现,并且避免了与其他二进制文件的名称冲突。
因此,一个合理的建议是:使仅在翻译单元中使用的函数定义仅对该翻译单元可见;以及定义在命名空间或类内公开的函数,以避免链接器中意外或不可预见的名称冲突/重复符号问题。
一个名为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';
}
链接器怎么可能知道在main
中调用哪个hello
?它不能,因为违反了一个定义规则。
在base_implementor_1.cpp中定义了一个名为hello
的全局函数。在base_iamplementor_2.cpp中又定义了另一个称为hello
的全局函数,这会导致多重定义和违反ODR所需的错误。为什么这是个问题?如果您有一个调用hello()
的第三个源文件,那么应该调用哪个函数?
如果您想在多个源文件中定义具有相同名称的不同函数,可以在它们前面加上static
关键字
static void hello() { }
或在匿名名称空间内
namespace {
void hello() { }
}