为什么 g++ 中的标准库在多次包含时不违反单一定义规则?
Why don't standard libraries in g++ violate the One Definition Rule when included multiple times?
对联动过程了解甚少,用模板进行多文件编译时手足无措类。我想将定义保存在一个文件中,将声明保存在另一个文件中。但是折腾了一天之后,我发现我应该把定义和声明放在同一个过渡单元中(see here)。但是我的代码仍然无法编译,如果我没有弄清楚,我会 post 另一个问题。
但这不是问题。问题是:一旦我读到“keeping definitions and declarations in the same header”,它被认为是不好的做法。其中一条评论说:
The include guards protect against including the same header file multiple times in the same source file. It doesn't protect against inclusion over multiple source files
有道理,直到我想用 g++ 中的标准库来尝试这个。我试着用 <vector>
来做,所以我有两个文件:
main.cpp:
#include "class.h"
#include <vector>
template class std::vector<int>;
int main()
{
std::vector<int> arr;
}
class.h:
#include <vector>
std::vector<int> a(4);
我用g++ main.cpp class.h
编译,编译成功。但为什么?我不是两次包含 <vector>
,因此我有双重定义吗?
我检查了我机器上的头文件,它们在同一个文件中有定义和声明。
您需要区分两类实体:在整个程序中可能只有一个定义的实体和在每个翻译单元中可能只有一个定义的实体,尽管这些定义必须相同(即相同的序列令牌加上一些更多的要求)。在任何情况下,一个翻译单元可能只包含一个实体的定义。
详情见https://en.cppreference.com/w/cpp/language/definition。
大多数实体属于第一类,但是 classes、各种模板和 inline
函数属于第二类。这些实体不仅 可以 在每个翻译单元中定义,而且它们通常 需要 在使用它们的每个翻译单元中定义。这就是为什么它们的定义通常属于 header 文件,而第一类实体的定义 从不 属于 header 文件。
std::vector
是一个class模板,所以它和它的成员函数可以在每个翻译单元中定义一次。
不清楚您对编译器调用 g++ main.cpp class.h
的意图是什么,但它实际上只编译一个翻译单元 (main.cpp
) 并为 class.h
做一些其他事情,因为它的文件结尾(请参阅您问题下的评论)。
在 main.cpp
的编译单元中,您没有包含 std::vector
或其成员的两个定义,尽管您有两个 #include<vector>
指令,因为需要标准库来防止这不会发生。它可以例如使用 header 守卫作为用户代码将实现相同的保证。
如果 class.h
也被编译为翻译单元,那么仍然不会有问题,因为 std::vector
及其成员是模板化实体,因此可以在这个翻译单元。
请注意,从技术上讲,标准库不受语言规则的约束。它可能根本不是用 C++ 编写的。在那种情况下,只要用户程序符合语言规则,它就必须为用户正确工作,无论它是如何包含的。
还要注意显式实例化定义
template class std::vector<int>;
您使用它的方式毫无意义。仅当 class 模板的成员与上述典型用法相反,未在 header 中定义,而是单个翻译单元时才需要(但标准库从来不是这种情况)或者加快编译时间,在这种情况下,其他翻译单元中应该有一个显式实例化声明,以防止隐式实例化。
对联动过程了解甚少,用模板进行多文件编译时手足无措类。我想将定义保存在一个文件中,将声明保存在另一个文件中。但是折腾了一天之后,我发现我应该把定义和声明放在同一个过渡单元中(see here)。但是我的代码仍然无法编译,如果我没有弄清楚,我会 post 另一个问题。
但这不是问题。问题是:一旦我读到“keeping definitions and declarations in the same header”,它被认为是不好的做法。其中一条评论说:
The include guards protect against including the same header file multiple times in the same source file. It doesn't protect against inclusion over multiple source files
有道理,直到我想用 g++ 中的标准库来尝试这个。我试着用 <vector>
来做,所以我有两个文件:
main.cpp:
#include "class.h"
#include <vector>
template class std::vector<int>;
int main()
{
std::vector<int> arr;
}
class.h:
#include <vector>
std::vector<int> a(4);
我用g++ main.cpp class.h
编译,编译成功。但为什么?我不是两次包含 <vector>
,因此我有双重定义吗?
我检查了我机器上的头文件,它们在同一个文件中有定义和声明。
您需要区分两类实体:在整个程序中可能只有一个定义的实体和在每个翻译单元中可能只有一个定义的实体,尽管这些定义必须相同(即相同的序列令牌加上一些更多的要求)。在任何情况下,一个翻译单元可能只包含一个实体的定义。
详情见https://en.cppreference.com/w/cpp/language/definition。
大多数实体属于第一类,但是 classes、各种模板和 inline
函数属于第二类。这些实体不仅 可以 在每个翻译单元中定义,而且它们通常 需要 在使用它们的每个翻译单元中定义。这就是为什么它们的定义通常属于 header 文件,而第一类实体的定义 从不 属于 header 文件。
std::vector
是一个class模板,所以它和它的成员函数可以在每个翻译单元中定义一次。
不清楚您对编译器调用 g++ main.cpp class.h
的意图是什么,但它实际上只编译一个翻译单元 (main.cpp
) 并为 class.h
做一些其他事情,因为它的文件结尾(请参阅您问题下的评论)。
在 main.cpp
的编译单元中,您没有包含 std::vector
或其成员的两个定义,尽管您有两个 #include<vector>
指令,因为需要标准库来防止这不会发生。它可以例如使用 header 守卫作为用户代码将实现相同的保证。
如果 class.h
也被编译为翻译单元,那么仍然不会有问题,因为 std::vector
及其成员是模板化实体,因此可以在这个翻译单元。
请注意,从技术上讲,标准库不受语言规则的约束。它可能根本不是用 C++ 编写的。在那种情况下,只要用户程序符合语言规则,它就必须为用户正确工作,无论它是如何包含的。
还要注意显式实例化定义
template class std::vector<int>;
您使用它的方式毫无意义。仅当 class 模板的成员与上述典型用法相反,未在 header 中定义,而是单个翻译单元时才需要(但标准库从来不是这种情况)或者加快编译时间,在这种情况下,其他翻译单元中应该有一个显式实例化声明,以防止隐式实例化。