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

Understanding what causes this multiple definition error

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

// 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() 函数。但事实似乎并非如此。任何人都可以告诉我链接器中发生了什么导致这个多重定义错误发生吗?

您在 base_implementor_1.cpp 中定义了一个名为 hello 的全局函数。您在 base_implementor_2.cpp 中定义了另一个名为 hello 的全局函数。这会导致多重定义和违反 ODR 所需的错误。为什么这是个问题?如果您有第三个源文件调用 hello(),应该调用哪个函数?

如果要在多个源文件中定义同名的不同函数,可以在它们前面加上static关键字

static void hello() { }

或在匿名命名空间内

namespace {
    void hello() { }
}

您在两个不同的翻译单元中对名为 hello 的函数有两个不同的定义。到了link的时候,linker不知道link是哪个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';
}

link 人怎么可能知道在 main 中呼叫哪个 hello?不能,因为违反了一个定义规则。

One-Definition-Rule定义两个范围的规则,即翻译单元范围和程序范围。

以下具有翻译单元范围的规则规定,同一个翻译单元不得包含同一函数的两个不同定义:

Only one definition of any variable, function, class type, enumeration type, or template is allowed in any one translation unit (some of these may have multiple declarations, but only one definition is allowed).

因此,如果您有两个不同的 .cpp 文件,那么您就有两个不同的翻译单元,并且每个翻译单元都可能有自己的 hello() 定义;在一个翻译单元的范围内不违反ODR。

以下程序作用域规则定义了 odr 使用的函数必须在程序中恰好定义一次:

One and only one definition of every non-inline function or variable that is odr-used (see below) is required to appear in the entire program (including any standard and user-defined libraries). The compiler is not required to diagnose this violation, but the behavior of the program that violates it is undefined.

odr-used 的定义非正式地指出,对于每个被调用的函数或获取地址的函数都必须在程序中定义:

Informally, an object is odr-used if its address is taken, or a reference is bound to it, and a function is odr-used if a function call to it is made or its address is taken. If an object or a function is odr-used, its definition must exist somewhere in the program; a violation of that is a link-time error.

因此,如果多个 .cpp 文件公开了 hello() 的实现,并且如果调用或引用了此函数,则显然违反了程序范围内的 ODR。

如果相应的函数未被 ODR 使用(即调用或引用),ODR 应该——据我所知——不被违反;

如果编译器抱怨重复符号,那是因为程序违反了链接规则(请就 "If I don't odr-use a variable" 提出 SO answer)。 C++11 §3.5[basic.link]/9 状态:

Two names that are the same and that are declared in different scopes shall denote the same variable, function, type, enumerator, template or namespace if

  • both names have external linkage or else both names have internal linkage and are declared in the same translation unit; and ...

为避免这种情况,请确保最多公开 hello() 的一个实现,并使所有其他实现 static 或使用未命名的命名空间。

在 C 编程语言中,static 与全局变量和函数一起使用以将它们的范围设置为包含文件,即它不会公开此实现并避免与其他二进制文件的名称冲突。

因此,一个合理的建议是:使仅在翻译单元内使用的函数定义仅对该翻译单元可见;并定义在命名空间或 class 中公开的函数,以避免链接器中意外或不可预见的名称冲突/重复符号问题。