【问题标题】:Why class redefinition in a several cpp files is permitted [duplicate]为什么允许在多个 cpp 文件中重新定义类[重复]
【发布时间】:2014-05-06 12:02:41
【问题描述】:

让我有两个 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 的重定义错误。但是在我重新定义的类的情况下,但没有引发错误。我很困惑。

【问题讨论】:

  • 您可以将这些类定义放在一个未命名的命名空间中。
  • @TejasPatel:实际上,问题是不同的。您链接的那个提供了一个不同的定义,而在这种情况下,这是一个相同的定义
  • @JAB:一切都不同。如果它们不同,则您违反了 ODR(一个定义规则);而如果它们相同,那没关系(这就是#include 的含义)。
  • @MatthieuM。哦,看来你是对的。我很抱歉。

标签: c++ class definition


【解决方案1】:

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

在您的第一个示例中,每个翻译单元都有自己的 class a 定义。由于翻译单元彼此分离,并且它们不会生成具有相同名称的全局运行时工件,因此可以。该标准要求每个翻译单元只有一个类的定义 - 请参阅第 3.2.1 和 3.2.4 节:

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

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

但是,该标准允许在不同的翻译单元中定义多个类 - 请参阅第 3.2.6 节:

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

接下来是一长串要求,归结为两个类定义必须相同;否则,程序被认为是格式错误的。

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

如果您声明变量static,您的程序将编译,因为静态变量对于定义它们的翻译单元是本地的。

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

【讨论】:

  • 感谢您的回答。但是您能从 c++ 标准中获得参考或引用吗?
  • @DmitryFucintv:C++11 3.2,一个定义规则,[basic.def.odr]。
  • 感谢大家的帮助! :D
  • 最后一段肯定是错的。声明变量static 会产生两个不同的、不相关的变量。两个同名的类是同一个类,即使它们是在两个不同的翻译单元中定义的。
  • @JamesKanze 我可以看到最后一段可能会产生误导。我进行了编辑以确保分别处理这两种情况。谢谢!
【解决方案2】:

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

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

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

【讨论】:

  • 请注意,您的第一个风格的多个定义(就像您描述的方式一样)会导致未定义的行为,因此不保证会出现错误(尽管通常会出现这种情况)。另外:一个nit,但不需要相同的来源——只需要相同的标记序列。 (例如,定义可以在 cmets 中有所不同。)
【解决方案3】:

除非定义在使用它的翻译单元中可用,否则不能使用类(除非以非常有限的方式)。这意味着您需要多个定义才能在多个单元中使用它,因此语言允许这样做 - 只要所有定义都相同。相同的规则适用于在使用时需要对其进行定义的各种其他实体(例如模板和内联函数)。

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

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

extern int a;

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

有关单一定义规则的完整详细信息,请参阅 C++11 3.2,[basic.def.odr]。

【讨论】:

  • 您可能应该在这前面加上“因为标准是这样说的。标准这么说的原因是......”。
  • @JamesKanze:确实,对于任何关于为什么某事被允许或不允许的问题的迂腐回答是“因为标准是这样说的”。我通常不觉得有必要说明这一点,也不认为这会改善这个答案。
猜你喜欢
  • 1970-01-01
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 2019-12-22
  • 1970-01-01
  • 2015-04-02
  • 2018-08-15
相关资源
最近更新 更多