了解导致此多重定义错误的原因

时间:2017-01-09 00:31:22

标签: c++ linker one-definition-rule

我有一个基类,它有一个由两个类实现的纯虚方法:

// 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函数。但事实似乎并非如此。谁能告诉我链接器中发生了什么事情才能发生这种多重定义错误?

3 个答案:

答案 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?它不能,因为违反了一个定义规则。