为什么跨cpp文件定义类不会导致链接器错误?

时间:2017-04-18 04:01:02

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

如果我的文件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() {} 不能。现在在第一个编译器错误(在一个cpp文件中两次class Foo{};),它说redefinition of foo,所以class Foo{};是一个定义。那么为什么,与功能不同,它可以在一个程序中定义两次?

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

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属于不同的类型。这两个都应该有外部链接,但这个代码是A-ok。

4 个答案:

答案 0 :(得分:2)

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

关于第二个问题,不同的定义不同 TU,这是违反ODR的行为。但是,那些是NDR。你的计划是 仍然是畸形的,它可能会导致奇怪的错误。

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

TheType ClassName::VariableName;

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

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

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

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

答案 1 :(得分:1)

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

还要注意definition与C ++中declaration的区别。

更新。在包含静态成员变量的新示例中,您只有声明而没有定义

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

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

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

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

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

$ clang -E foo.cpp >foo.ii

答案 2 :(得分:0)

每个cpp文件独立编译所有定义。它们与编译器的观点在两个不同的cpp文件中定义两次,或者在共享包含中一次没有区别。

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

答案 3 :(得分:-1)

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