为什么一个定义规则,而不是一个声明规则?

Why One Definition Rule, not One Declaration Rule?

我已阅读以下材料:

https://www.wikiwand.com/en/One_Definition_Rule

http://en.cppreference.com/w/cpp/language/definition

What is the difference between a definition and a declaration?

但是还是想不通为什么是一定义规则而不是一声明规则?

我认为声明是定义的子集,因此 一个定义规则 就足够了。

因为同一个声明,在一个.h文件中,可能包含在多个编译单元中,而且因为多个定义肯定是编程错误,而多个声明则不是。

一个声明规则过于严格,会阻止多次使用相同 header 的程序进行编译。这也使得无法使用反向引用定义数据结构。

查看第一点(使用 headers)的一个简单方法是考虑一个由两个翻译单元组成的程序,A.cppB.cpp,它们都包含 <string> header.

翻译单元 A.cppB.cpp 是独立翻译的。通过包含 <string>,两个翻译单元都获得了 std::string.

的声明

至于第二点(具有反向引用的数据结构),请考虑定义一棵树的示例,其中每个节点都有对其 parent 树的反向引用:

// Does not compile
struct tree {
    struct node *root;
};
struct node {
    struct node *left;
    struct node *right;
    struct tree *owner;
};

此示例无法编译,因为 struct node *tree 中的 node 未声明。切换 struct nodestruct tree 声明的顺序不会有帮助,因为这样 struct tree *owner 中的 tree 将不会被声明。 C 和 C++ 中的唯一解决方案是为两个 struct 中的任何一个引入第二个声明。

因为当你在头文件中声明了一个函数

// header toto.h
int f(void);

而你想在它所属的编译单元中定义它,你会做

#include "toto.h"

int f(void) {
   return 0;
}

定义也是一个声明,所以这个编译单元看到两个声明,一个在头文件中,一个在.c.cpp文件中。

简而言之,多重声明规则允许检查不同源文件之间的一致性。

真正的原因是 C++ 翻译模型可以轻松处理冲突的多个声明;它只需要工具集的编译器部分来检测源代码中的此类错误:

int X();
void X(); // error

编译器可以轻松做到这一点。

当任何翻译单元中都没有这样的错误时,就没有问题;每个翻译单元中的每个 X() 调用都是相同的;剩下要做的就是 linker 每次呼叫到一个正确的目的地时 link。声明已经完成了它们的工作,不再起作用。


现在有了多个定义,没那么容易了。定义涉及多个翻译单元,超出了编译阶段的范围。

我们已经在上面的示例中看到了这一点。 X() 调用已经到位,但现在我们需要保证它们都到达相同的目的地,相同的 X().[=14] 的 定义 =]

这样的定义只能有一个应该很清楚,但是如何执行呢?简单的说,到link目标代码在一起的时候,源代码已经处理好了。

答案是C++基本选择把负担推给程序员。强制 compiler/linker 实施者检查所有多个定义是否相等并检测差异将超出 C++ 工具集在大多数现实生活中的能力,或者完全破坏这些工具的工作方式,因此实用的解决方案是禁止它 and/or 强制程序员确保它们都是相同的,否则会出现未定义的行为。

定义是声明的子集,而不是相反。每个定义都是声明,也有不是定义的声明。

int i = 3;      // definition and declaration
extern int i;   // ok: (re)declaration
int i = 4;      // error: redefinition

extern int j;   // declaration
extern int j;   // ok: (re)declaration
int j = 5;      // ok: (re)declaration and definition
int j = 6;      // error: redefinition