代码之家  ›  专栏  ›  技术社区  ›  Learpcs

为什么g++中的标准库在多次包含时不违反一条定义规则?

  •  0
  • Learpcs  · 技术社区  · 2 年前

    我对链接过程知之甚少,我正在用模板类进行多文件编译。我想在一个文件中保存定义,在另一个文件中保存声明。但在经历了一天的痛苦之后,我发现我应该把定义和声明放在同一个过渡单元中( see here ).但我的代码仍然无法编译,如果我没有弄明白,我会发布另一个问题。

    但这不是问题所在。问题是:有一次我读到 keeping definitions and declarations in the same header “这被认为是不好的做法。其中一条评论说:

    include防护可防止在同一源文件中多次包含同一头文件。它不能防止包含多个源文件

    这是有道理的,直到我想用g++中的标准库来尝试这一点。我试着用 <vector> ,所以我有两个文件:

    主要的cpp:

    #include "class.h"
    #include <vector>
    
    template class std::vector<int>;
    
    int main()
    {
        std::vector<int> arr;
    }
    

    班h:

    #include <vector>
    
    std::vector<int> a(4);
    

    我用 g++ main.cpp class.h 它编译成功了。但为什么呢?我没有包括在内吗 <向量> 两次,所以我有双重定义?

    我检查了我机器上的头文件,它们在同一个文件中有定义和声明。

    1 回复  |  直到 2 年前
        1
  •  4
  •   user17732522    2 年前

    您需要区分两类实体:在整个程序中可能只有一个定义的实体和在每个翻译单元中可能只有一个定义的实体,尽管这些定义必须相同(即相同的标记序列加上一些其他要求)。在任何情况下,单个翻译单元只能包含一个实体的定义。

    看见 https://en.cppreference.com/w/cpp/language/definition 详细信息。

    大多数实体属于第一类,但类、各种模板和 inline 函数属于第二个函数。不仅 可以 这些实体可以在每个翻译单元中定义,但它们通常 需要 在使用它们的每个翻译单元中定义。这就是为什么它们的定义通常属于头文件,而第一类实体的定义属于头文件 从不 属于头文件。

    std::vector 是一个类模板,因此它及其成员函数可以在每个翻译单元中定义一次。

    不清楚您对编译器调用的意图是什么 g++ main.cpp class.h 是的,但它实际上只编译一个翻译单元( main.cpp )为你做点别的 class.h 因为它的文件结尾(参见问题下方的注释)。

    在编译单元中 主要的cpp 你没有包括两个定义 向量 或者它的成员,尽管你有两个 #include<vector> 指令,因为需要标准库来防止这种情况发生。例如,它可以像用户代码一样使用标题保护,以实现相同的保证。

    如果 班H 也会被编译成一个翻译单元,那么就不会有问题了,因为 向量 它的成员是模板化的实体,因此可以在这个翻译单元中再次定义它们。


    请注意,从技术上讲,标准库不受语言规则的约束。它可能根本不是用C++编写的。在这种情况下,只要用户程序符合语言规则,它就必须为用户正确工作,无论它是如何包含的。


    还要注意,显式实例化定义

    template class std::vector<int>;
    

    你使用它的方式毫无意义。仅当类模板的成员与上面提到的典型用法相反,不是在头中定义的,而是在单个翻译单元中定义的(但对于标准库来说,情况并非如此)或是为了加快编译时间时,才需要这样做,在这种情况下,其他翻译单元中应该有一个显式实例化声明,以防止隐式实例化。