我已阅读以下材料:
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?
但是,仍然无法弄清楚为什么它是一个定义规则而不是一个声明规则?
我认为声明是定义的一个子集,所以一个定义规则就足够了。
答案 0 :(得分:7)
一个声明规则太严格,阻止编译多次使用相同标头的程序。这也使得无法使用反向引用来定义数据结构。
查看第一个点(使用标题)的一种简单方法是考虑由两个翻译单元A.cpp
和B.cpp
组成的程序,这两个翻译单元都包含<string>
标题。
翻译单元A.cpp
和B.cpp
是独立翻译的。通过加入<string>
,两个翻译单元都会获得std::string
的声明。
关于第二点(具有反向引用的数据结构),考虑一个定义树的示例,其中每个节点都具有对其父树的反向引用:
// Does not compile
struct tree {
struct node *root;
};
struct node {
struct node *left;
struct node *right;
struct tree *owner;
};
此示例无法编译,因为来自node
的{{1}}未声明。切换struct node *tree
和struct node
声明的顺序不会有帮助,因为来自struct tree
的{{1}}将是未声明的。 C和C ++中唯一的解决方案是为两个tree
中的任何一个引入第二个声明。
答案 1 :(得分:6)
因为.h文件中的相同声明可能包含在多个编译单元中,并且因为多个定义肯定是编程错误,而多个声明不是。
答案 2 :(得分:4)
定义是声明的子集,而不是相反。每个定义都是一个声明,并且声明不是定义。
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
答案 3 :(得分:2)
因为在头文件中声明了一个函数
// header toto.h
int f(void);
并且您希望在它所属的编译单元中定义它,您可以
#include "toto.h"
int f(void) {
return 0;
}
该定义也是一个声明,因此该编译单元看到两个声明,一个在标题中,一个在.c
或.cpp
文件中。
简而言之,多声明规则允许检查不同源文件之间的一致性。
答案 4 :(得分:1)
原因确实是C ++翻译模型可以轻松处理冲突的多个声明;它只需要工具集的编译器部分来检测源代码中的错误:
int X();
void X(); // error
编译器可以很容易地做到这一点。
如果在任何翻译单元中没有这样的错误,那么没有问题;每个翻译单元中的每个X()
电话都是相同的;剩下要做的是链接器将每个调用链接到一个正确的目标。宣言已经完成了他们的工作,不再发挥作用。
现在有了多个定义,这并不容易。定义涉及多个翻译单元,超出了编译阶段的范围。
我们已经在上面的示例中看到了这一点。 X()
电话已到位,但现在我们需要保证它们都在同一目的地,X()
的定义。
可能只有一个这样的定义应该是明确的,但如何强制执行呢?简单来说,当它将目标代码链接在一起时,源代码已经处理完毕。
答案是C ++基本上选择给程序员带来负担。在大多数现实情况下,强制编译器/链接器实现者检查所有多个定义的相等性和检测差异将超出C ++工具集的功能,或完全打破这些工具的工作方式,因此实用的解决方案是禁止它和/或强迫程序员确保它们都是相同的,否则会得到不确定的行为。