在 C++ 中,头文件到底是什么以及在实现文件中是什么?

Whi*_*zil 5 c++ c++11

我有一个自定义 Stack 实现的示例。据我了解,.h文件应该只包含声明,cpp文件应该只包含implementations。我在cplusplus.com/stack_example上找到了一个自定义堆栈的例子,它看起来像下面这样。

堆栈.h文件

#ifndef _STACK_H_
#define _STACK_H_

#include <iostream>
#include "Exception.h"

template <class T>
class Stack {
    public:
        Stack():top(0) {
            std::cout << "In Stack constructor" << std::endl;
        }
        ~Stack() {
            std::cout << "In Stack destructor" << std::endl;
            while ( !isEmpty() ) {
                pop();
            }
            isEmpty();
        }

        void push (const T& object);
        T pop();
        const T& topElement();
        bool isEmpty();

    private:
        struct StackNode {              // linked list node
            T data;                     // data at this node
            StackNode *next;            // next node in list

            // StackNode constructor initializes both fields
            StackNode(const T& newData, StackNode *nextNode)
                : data(newData), next(nextNode) {}
        };

        // My Stack should not allow copy of entire stack
        Stack(const Stack& lhs) {}

        // My Stack should not allow assignment of one stack to another
        Stack& operator=(const Stack& rhs) {}
        StackNode *top;                 // top of stack

};
Run Code Online (Sandbox Code Playgroud)

现在我有疑问。这个.h文件显然揭示了一些实现细节。构造函数和析构函数都在文件中实现.h。根据我的理解,这些应该在.cpp文件中实现。此外,还有的是,struct StackNode这也实现了在.h文件中。甚至可以在.cpp文件中实现并只在头文件中声明它吗?作为一般规则,如果这些在.cpp实现文件中不是更好吗?什么是编写这个东西以使其遵循 C++ 规则的最佳方法?

Pao*_*o M 5

对于头文件中必须包含哪些内容,没有标准规则,标准库头文件除外。我的意思是,从理论上讲,我们可以完全避免头文件以及在文件中复制和粘贴声明。.cpp

也就是说,这更多的是常识和经验的问题。.cpp您可以根据自己的意愿和需要将内容放入 headers 或s 中。我们可以列出一些用例:

  • 模板(如您的示例中所示)声明和实现通常位于标头中。请参阅此线程以获取更多信息。

  • 当您认为/希望函数声明不仅仅用于翻译单元时,您确实可以将函数声明放入标头中

  • 当您不希望/认为函数声明在其他翻译单元中使用时,不要将函数声明放入标头中

  • 如果您想要一个函数inline并希望该函数在不同的翻译单元中使用,那么您可以将其定义放在标头中(inline如果它是非成员函数,则在关键字前面)

  • 当您认为/希望从不同的翻译单元访问该类型的对象时,您确实在标头中放置了类声明(也称为前向声明)

  • 当您认为/希望仅在一个翻译单元中访问该类时,不要将类声明放入标头中

  • 当您认为/希望在不同的翻译单元中创建该类型的对象时,您确实将类定义(即整个类接口)放入标头中

  • 当您希望仅在一个翻译单元中创建该类型的对象时,不要将类定义放入标头中

  • 如果您想定义一个全局变量,您几乎不需要将定义放入头文件中(除非您想将该头包含在单个翻译单元中)

  • 如果您正在开发一个库,您将把想要提供给用户的那些函数和类的声明放入头文件中

  • .cpp如果您正在开发一个库,您会找到一种方法将您不想公开的实现细节放入文件中(正如 @Joachim Pileborg 建议的那样,请参阅pimpl idiom)

  • 您通常不想将using 声明或using 指令放在标头中,因为它们会污染那些将#include标头包含在内的翻译单元

  • 如果可能的话,您不希望将#include其他标头添加到您的标头中;你肯定更喜欢转发声明你需要什么来让你的程序编译

  • 最后,粗略地说,标头中放入的内容越少,文件的编译速度就越快;而且,让我说显而易见的事情,您确实希望您的文件能够快速编译!


笔记

上面我主要讨论了类和函数,但一般来说,这些经验法则对于枚举和typedef声明都是有效的。

列表中缺少标头中的成员函数定义,因为这是类定义内部或外部的函数定义问题,而不是.hvs.cpp文件的问题

*访问的意思是通过指针或引用使用,与创建的相反


Com*_*sMS 5

头文件与源文件没有本质区别。

原则上,标头可以包含与源文件相同的代码结构。按照惯例,将标头与源文件区分开来的唯一一件事是标头应该#include被其他文件 d,我们通常不会对源文件这样做(尽管我们可以,如果我们喜欢冒险的话)。

现在重要的是,如果一个文件获取会发生什么#include用d以上的源文件。请注意,这基本上相当于将相同的代码复制粘贴到多个 .cpp 文件中。在这种情况下,有些事情可能会让您陷入困境。

特别是,如果您最终在两个不同的源文件中对同一符号有两个定义,则您的程序格式不正确(根据一个定义规则),链接器可能会拒绝链接您的程序。

对于不同的源文件,这通常不是问题,除非您不小心在不同的上下文中重用了一个名称。一旦头文件进入图片(这只是复制粘贴的代码),事情就开始不同了。您仍然可以将定义放在头文件中,但是如果该头文件随后被多个源文件拉入,则您自己就违反了一个定义规则。

因此,约定是只将内容放入可以安全地跨多个源文件复制的头文件中。这包括声明、内联函数定义和模板。

这里的关键实现可能是头文件是一个相当古老的工具,用于在源文件之间共享代码。因此,他们严重依赖用户足够聪明,不会把事情搞砸。