2017-04-18 75 views
7

如果我有一个文件foo.cpp用下面的代码:为什么跨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,命名的类有外部链接。那么为什么在这两个cpp文件之间没有冲突class Foo

编辑2:根据上面链接的网站,不仅命名类有外部链接,但它也是静态成员。然而,这一切编译罚款:

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-好的。

+0

那是什么编译器? –

+3

此问题[已在此处讨论](http://stackoverflow.com/questions/6465325/do-classes-have-external-linkage),tl; dr;当具有相同名称的类(枚举,联合等)分别在不同的翻译单元中定义时,它们具有内部链接或没有链接,因此不会发生错误。请注意,接受的答案是错误的,并且有更多upvotes的答案是正确的。 – VTT

+0

@ΦXocę웃ПетаоApple LLVM V7.3.0(clang-703.0.31) – rcplusplus

回答

2

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

关于第二个问题,在不同的 TU的不同定义,这是一个ODR-冲突。但是,那些是NDR。您的程序 仍然格式错误,并且可能会导致奇怪的错误。

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

TheType ClassName::VariableName; 

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

有一个例外,conststatic数据成员 内联初始值设定项。


ODR =一个定义规则
TU =翻译股
NDR =没有诊断所需

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

+0

不需要诊断,这是什么意思? – rcplusplus

+0

@rcplusplus查看我更新的答案 –

-1

正常情况下,类在头文件中定义,如果你这样做,那么包含头文件时就会出错。

+4

这并没有回答这个问题。 –

+1

它回答了这样一个问题:如果这产生了一个链接器错误,比不可能在具有多个单元的项目中使用类,因此它不会产生错误! –

+0

我知道头文件这样做,这就是为什么我问。如果你把一个变量放在一个头文件中,你会得到多个定义错误,而不是一个类。 – rcplusplus

0

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

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

+0

最后一句并不完全正确,因为您可以手动将函数标记为“inline”,并在类体外实现它们。 – chtz

1

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

还要注意如何definitiondeclaration区别C++。

Upd。在您的新的例子有静态成员变量你只有声明没有定义

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

一个可以复制翻译单元内声明,但没有定义。的类型(包括类)

定义可以在不同的翻译单元,函数和变量具有外部链接被复制 - 不。

两种类型有两种翻译单元名称相同,但不同结构的定义是ODR违反该接头通常不能诊断 - 你的程序是不正确,但所有的“构建就好了”。

翻译单元是编译器在预处理后得到的输入。使用铛或GCC,你可以得到这样的:

$ clang -E foo.cpp >foo.ii 
+0

定义和声明我很满意,仅仅是因为某种原因,类定义不是我认为的。根据ODR的说法,只要多个文件重新定义班级,只要定义保持不变即可。然而在我的编辑中,我展示了代码(我相信)违反了这一点。 – rcplusplus

+0

@rcplusplus是的,如果标准没有强制执行诊断并且编译器选择不报告,那么您可以编写违反标准的代码并从编译器中获得完全的静默。这不会使代码破碎。 –

相关问题