【问题标题】:Why does defining a class across cpp files not cause a linker error?为什么跨 cpp 文件定义类不会导致链接器错误?
【发布时间】:2017-04-18 04:01:02
【问题描述】:

如果我有一个文件foo.cpp,代码如下:

class Foo {
};

class Foo {
};

int main() {
    return 0;
}

那么我自然会得到error: redefinition of 'Foo'。但是,如果我有 foo.cpp

class Foo {
};

int main() {
    return 0;
}

bar.cpp一起

class Foo {
};

尽管class Foo 在整个程序中被定义了两次,但整个过程编译得很好。

如果我将int something; 放在全局命名空间的两个文件中,那么我会得到一个链接器错误(特别是duplicate symbol),但对于类定义,这永远不会发生。

我知道 int doIt(); 这样的函数声明可以在两个 cpp 文件中重复,但是 定义,例如int doIt() {} 不能。现在在第一个编译器错误中(class Foo{}; 在一个 cpp 文件中有两次),它说redefinition of foo,所以class Foo{}; 是一个定义。那么为什么与函数不同,它可以在一个程序中定义两次呢?

编辑:根据this website,命名类具有外部链接。那么为什么class Foo 在两个 cpp 文件中没有冲突呢?

EDIT2: 根据上面链接的网站,命名类不仅有外部链接,静态成员也有。然而这一切都编译得很好:

foo.cpp:

class Foo {
public:
    int foo();
    static int x;
};

int Foo::foo() {
    return 5;
}

int main() {
    return 0;
}

bar.cpp:

class Foo {
public:
    int foo(int);
    static bool x;
};

int Foo::foo(int i) {
    return i * 2;
}

不仅Foo::foo 被重新定义为不同的签名,而且Foo::x 是不同的类型。这两个都应该有外部链接,但是这个代码是好的。

【问题讨论】:

  • 那是什么编译器?
  • 这个问题has been discussed here, tl;dr;当具有相同名称的类(枚举、联合等)在不同的翻译单元中单独定义时,它们具有内部链接或没有链接,因此不会发生错误。请注意,接受的答案是错误的,而获得更多支持的答案是正确的。
  • @ΦXocę웃Пepeúpaツ Apple LLVM 版本 7.3.0 (clang-703.0.31)
  • 您是否对标准的相应规则感兴趣,或者这样做的动机?
  • @VTT 你链接到了正确的问题,指出了正确的答案,然后总结错了。

标签: c++ class c++11 linker


【解决方案1】:

关于您的第一个问题,定义相同 多个 TU,这是 ODR 明确允许的,否则 语言是没用的。

关于第二个问题,不同的定义不同 TU,这是违反 ODR 的。但是,这些是 NDR。你的程序是 仍然格式不正确,并且可能会导致奇怪的错误。

关于第三个问题,static 数据成员,这些是 声明,而不是定义。他们需要一个独特的定义,例如:

TheType ClassName::VariableName;

这些通常放在随附的 .cpp 文件中。

有一个例外,const static 数据成员 内联初始化器。


ODR = 一个定义规则
TU = 翻译单元
NDR = 无需诊断

关于 NDR 的说明;某种错误对编译器来说很难 检测,标准通常不要求编译器 在这些情况下发出诊断(即警告或错误)。有 可以检测编译器的许多错误的工具,例如 CppLint 不能。当涉及 ODR 违规时,通常可以避免 只在标题中定义类型。

【讨论】:

【解决方案2】:

因为 C++ 中的“One Definition Rule”。您不能在一个翻译单元中重新定义类,但可以(并且应该)在每个使用它的翻译单元中定义类。这就是 C/C++ 中存在标头和#include 的原因。您应该将类​​定义放在标题中,并将其包含在每个使用它的 .cpp 中。它可以防止 ODR 违规,但从技术上讲,使用 #include 与定义每个 .cpp 文件中的类相同(预处理器只是使包含的文件成为编译文件的一部分)。

还要注意 definition 与 C++ 中的 declaration 有何不同。

更新。在您的带有静态成员变量的新示例中,您只有 declarations 而没有 definition

class Foo {
public:
    static int x; // <-- variable declaration
};
int Foo::x; // <-- variable definition

可以在翻译单元中重复声明,但不能重复定义。

类型(包括类)的定义可以通过外部链接在不同的翻译单元、函数和变量中重复 - 不是。

在两个具有相同名称但结构不同的翻译单元中定义两种类型是 ODR 违规,链接器通常无法诊断 - 您的程序不正确,但所有“构建都很好”。

翻译单元是编译器在预处理后作为输入得到的。使用 clang 或 gcc 你可以得到它:

$ clang -E foo.cpp >foo.ii

【讨论】:

  • 定义和声明我很满意,只是由于某种原因,类定义不是我认为的那样。根据 ODR,跨多个文件重新定义一个类只有在定义保持不变时才可以。然而,在我的编辑中,我展示的代码(我相信)违反了这一点。
  • @rcplusplus 是的,如果标准不要求诊断并且编译器选择不报告诊断,您可以编写违反标准的代码并从编译器获得完全沉默。这并没有减少代码的损坏。
【解决方案3】:

每个 cpp 文件独立编译所有定义。从编译器的角度来看,它们在两个不同的 cpp 文件中定义两次或在共享的包含中定义一次没有区别。

类定义本身不会引入任何用户定义的符号,因此不会产生链接器错误。只有类方法和静态成员可以。如果方法是在类定义中定义的,则它们被视为内联。标记内联函数,以便链接器将选择任何可用的定义并假设它与所有其他定义等效。如果这些方法未在类定义中定义且未标记为内联,则多个实例将导致链接器错误。

【讨论】:

  • 最后一句并不完全正确,因为您可以手动将函数标记为inline并在类体之外实现它们。
【解决方案4】:

通常类是在头文件中定义的,如果这样做,那么在包含头文件时会出错。

【讨论】:

  • 这没有回答问题。
  • 它在某种程度上回答了这个问题:如果这产生了链接器错误,那么就不可能在具有多个单元的项目中使用类,因此它不会产生错误!
  • 我知道头文件会这样做,这就是我问的原因。如果将变量放在头文件中,则会出现多个定义错误,但不会出现类。
  • @rcplusplus 变量定义创建对象,函数定义创建代码 - 都是(命名的)链接器对象。但是类/类型定义不会生成链接器对象,因此您在链接期间不会发生名称冲突。但是类的定义在所有链接的目标文件中必须相同
  • @rcplusplus 我不认为它“工作正常”。对象具有不同的二进制结构。链接器只是无法检测到 ODR 违规 - 它只能使用“符号”(即名称)运行
猜你喜欢
  • 2019-12-22
  • 2021-04-22
  • 1970-01-01
  • 2015-05-20
  • 1970-01-01
  • 1970-01-01
  • 2015-06-29
  • 2013-07-09
  • 1970-01-01
相关资源
最近更新 更多