为什么允许在几个cpp文件中重新定义类

Why class redefinition in a several cpp files is permitted

本文关键字:新定义 文件 定义 几个 为什么 cpp      更新时间:2023-10-16

我有两个cpp文件:

//--a.cpp--//
class A
{
public:
    void bar()
    {
        printf("class A");
    }
};
//--b.cpp--//
class A
{
public:
    void bar()
    {
        printf("class A");
    }
};

当我编译和链接这些文件在一起,我没有错误。但是如果我这样写:

//--a.cpp--//
int a;
//--b.cpp--//
int a;

编译和链接这些来源后,我有一个错误作为a的重新定义。但在类的情况下,我已经重新定义,但没有错误引发。我困惑。

类是类型。在大多数情况下,它们是编译时工件;另一方面,全局变量是运行时工件。

在您的第一个示例中,每个翻译单元都有自己的class a定义。由于翻译单元彼此独立,并且它们不会产生具有相同名称的全局运行时构件,所以这是可以的。该标准要求每个翻译单元只有一个类的定义——参见第3.2.1和3.2.4节:

任何翻译单元不得包含任何变量、函数、类类型、枚举类型或模板的一个以上定义。

如果在翻译单元中使用类的方式要求类类型是完整的,则该类只需要一个定义。

然而,标准允许在单独的翻译单元中定义多个类——参见第3.2.6节:

类类型、枚举类型、具有外部链接的内联函数、类模板、非静态函数模板、类模板的静态数据成员、类模板的成员函数或用于的模板特化可以有多个定义如果每个定义出现在不同的翻译单元中,并且这些定义满足以下要求,则在程序中没有指定某些模板参数。[…]

后面是一长串要求,可以归结为两个类的定义必须相同;否则,程序被认为是病态的。

在第二个示例中,您在两个翻译单元中定义了一个全局运行时工件(变量int a)。当链接器试图生成最终输出(可执行文件或库)时,它会找到这两个输出,并发出重定义错误。注意,上面的规则3.2.6不包括带有外部链接的变量。

如果你声明你的变量static,你的程序将编译,因为静态变量是局部的翻译单元,在其中定义它们。

虽然两个程序都可以编译,但它们编译的原因是不同的:如果有多个类定义,编译器假定两个类是相同的;在第二种情况下,编译器认为这两个变量是相互独立的。

一个定义规则实际上有两种不同的风格。

一种风格,适用于全局变量和命名空间变量、静态类成员和没有inline关键字的函数,它说整个程序中只能有一个定义。这些内容通常放在*.cpp文件中。

另一种风格适用于类型定义、使用inline关键字声明的函数以及任何带有模板参数的东西,它认为定义可以在每个翻译单元中出现一次,但必须使用相同的源定义,并且在每个翻译单元中具有相同的含义。复制粘贴到两个*.cpp文件中是合法的,但通常你会把这些东西放在头文件和#include中,这些头文件来自所有需要它们的*.cpp文件。

类不能被使用(除非在非常有限的情况下),除非定义在使用它的翻译单元中可用。这意味着为了在多个单位中使用它,你需要多个定义,所以语言允许这样做——只要所有的定义都是相同的。同样的规则也适用于在使用时需要定义的各种其他实体(如模板和内联函数)。

通常,您可以通过将其放在头文件中,并在需要的地方包含它来共享定义。

变量只能与声明一起使用,而不能与定义一起使用,因此不需要允许多个定义。在您的情况下,您可以通过将其中一个声明为纯声明来修复错误:

extern int a;

使得只有一个定义。同样,这类声明通常放在头文件中,以确保它们在每个使用它们的文件中都是相同的。

关于一个定义规则的完整详细信息,请参见c++ 11 3.2, [basic.def.odr].