【问题标题】:Difference between implementing a class inside a .h file or in a .cpp file在 .h 文件或 .cpp 文件中实现类之间的区别
【发布时间】:2010-12-21 01:09:31
【问题描述】:

我想知道仅在头文件中声明和实现类与在头文件中对类进行原型化并在有效的 .cpp 文件中实现的常规方法相比有哪些区别。

为了更好地解释我在说什么,我指的是正常方法之间的差异:

// File class.h
class MyClass 
{
private:
  //attributes
  public:
  void method1(...);
  void method2(...);
  ...
};

//file class.cpp
#include "class.h"

void MyClass::method1(...) 
{
  //implementation
}

void MyClass::method2(...) 
{
  //implementation
}

和 just-header 方法:

// File class.h
class MyClass 
{
private:
  //attributes
public:
  void method1(...) 
  {
      //implementation
  }

  void method2(...) 
  {
    //implementation
  }

  ...
};

我可以得到主要区别:在第二种情况下,代码包含在需要它生成相同实现的更多实例的每个其他文件中,因此是隐式冗余;而在第一种情况下,代码是自行编译的,然后每个引用MyClass 对象的调用都链接到class.cpp 中的实现。

但是还有其他区别吗?根据情况使用一种方法而不是另一种方法是否更方便?我还在某处读到,将方法的主体直接定义到头文件中是对编译器的内联该方法的隐式请求,是真的吗?

【问题讨论】:

  • 如果你有适当的预处理器保护,它不会创建更多相同实现的实例。
  • 你是在说#ifndef CLASS_H #define CLASS_H 等吗?
  • @Andrew:当然会,如果两个 .cpp 文件都包含相同的头文件,那么无论任何预处理器保护如何,每个文件都将完整编译其内容,并且编译后的内容将出现在输出中目标文件。重复项只会由链接器删除。预处理器守卫将优雅地处理单个翻译单元对标头的双重包含,而不是在翻译单元之间共享。
  • 好的,所以它按我的想法工作。使用#define 将避免仅在同一翻译单元内重复定义..

标签: c++ class header


【解决方案1】:

对包含实现的标头的任何更改都将强制包含该标头的所有其他类重新编译和重新链接。

由于头文件的更改频率低于实现,因此通过将实现放在单独的文件中,可以节省大量编译时间。

正如其他一些答案已经指出的那样,是的,在文件的 class 块中定义方法将导致编译器内联。

【讨论】:

    【解决方案2】:

    是的,在类定义中定义方法等同于声明它们inline。没有其他区别。在头文件中定义所有内容没有任何好处。

    在带有模板类的 C++ 中通常会看到类似的情况,因为模板成员定义也必须包含在头文件中(因为大多数编译器不支持 export)。但是对于普通的非模板类,这样做是没有意义的,除非你真的想将你的方法声明为inline。

    【讨论】:

    • 这样做的明显一点是它更短。考虑到缺点,这并不是公共标头的真正好理由。但是对于在单个模块内部使用的小型实用程序类,它完全没问题。
    • @Pavel:您似乎在谈论在 cpp 文件中实现(声明和定义)的类(即根本没有标题声明)。当我阅读问题的主题时,我最初也认为问题将与此有关。但事实证明,问题实际上是不同的。此外,我不明白为什么我应该将内部(仅一个翻译单元)类的方法声明为 inline,即使它更短。
    • 事实上不准确。问题在于是否将函数定义放在单独的翻译单元中。这意味着隐式内联之外的几个明显差异。所以说“没有其他区别”要么完全错误,要么(更有可能)错过了一半的问题。
    • 嗯,它暗示inline(不包括模板案例)。无论是隐式 inline 还是显式 inline 对我来说都不是关键的区别。
    • 这不是我在争论的问题,我在明确地回答这个问题,“这是在头文件中声明和实现类之间的区别,与正常方法相比在其中您在标头中对类进行原型化并在有效的 .cpp 文件中实现。”,您说的是“内联”。我认为您正在回答“在类 defn 中定义的函数之间有什么区别”,而不是“在标题中定义的函数之间有什么区别”。
    【解决方案3】:

    是的,编译器会尝试内联直接在头文件中声明的方法,例如:

    class A
    {
     public:
       void method()
       {
       }
    };
    

    我可以想到以下在头文件中分离实现的便利:

    1. 您不会因为多个翻译单元中包含相同的代码而导致代码膨胀
    2. 您的编译时间将减少 剧烈地。请记住,对于任何 头文件中的修改 编译器必须构建所有其他 直接或间接的文件 包括它。我想这会很 令人沮丧的任何人构建 再次整个二进制文件只是为了添加一个 头文件中的空格。

    【讨论】:

      【解决方案4】:

      主要的实际区别在于,如果成员函数定义在头文件的主体中,那么当然对于包含该头文件的每个翻译单元,它们都会编译一次。当您的项目包含数百或数千个源文件,并且所讨论的类被广泛使用时,这可能意味着大量重复。即使每个类只被 2 或 3 个其他人使用,标题中的代码越多,要做的工作就越多。

      如果成员函数定义在自己的翻译单元(.cpp 文件)中,则编译一次,仅编译多次函数声明。

      确实,在类定义中定义(不仅仅是声明)的成员函数是隐式的inline。但是inline 并不意味着人们可能会合理地猜到它的意思。 inline 表示函数的多个定义出现在不同的翻译单元中是合法的,然后再链接在一起。如果该类位于不同源文件将要使用的头文件中,则这是必要的,因此该语言试图提供帮助。

      inline 也是对编译器的一个提示,即可以有效地内联该函数,但尽管有名称,但它是可选的。你的编译器越复杂,它就能够更好地做出关于内联的决定,并且它对提示的需求就越少。比实际的内联标记更重要的是该函数是否对编译器可用。如果函数是在不同的翻译单元中定义的,那么在编译调用它时它不可用,因此如果有任何东西要内联调用,那么它必须是链接器,而不是编译器。

      通过考虑第三种可能的方法,您可能能够更好地看到差异:

      // File class.h
      class MyClass
      {
          private:
              //attributes
          public:
             void method1(...);
             void method2(...);
             ...
      };
      
      inline void MyClass::method1(...)
      {
           //implementation
      }
      
      inline void MyClass::method2(...)
      {
           //implementation
      }
      

      现在隐式内联已经不存在了,这种“所有标题”方法与“标题加源”方法之间仍然存在一些差异。您如何在翻译单元之间划分代码会对构建时发生的事情产生影响。

      【讨论】:

      • 事实上我在谈论“对编译器的请求”,我知道这是可选的有效内联..
      • 是的,我重复一遍,以防任何 C++ 新手在读完我的前三段时忘记了。
      【解决方案5】:

      在过去,我创建了一个模块来屏蔽各种 CORBA 发行版的差异,并且期望它可以在各种 OS/编译器/CORBA lib 组合上统一工作。在头文件中实现它可以更容易地通过简单的包含将其添加到项目中。当调用它的代码需要重新编译时,即使用不同的库或在不同的操作系统上编译它时,相同的技术保证了代码在同一时间重新编译。

      所以我的观点是,如果您有一个相当小的库,预计可以在各种项目中重用和重新编译,使其成为头文件在与其他一些项目集成方面具有优势,而不是向主项目添加额外文件或重新编译一个外部 lib/obj 文件。

      【讨论】:

        【解决方案6】:

        对我来说,主要区别在于头文件就像类的“接口”,告诉该类的客户它的公共方法(它支持的操作)是什么,而不用担心客户的具体实现那些。从某种意义上说,它是一种将客户端从实现更改中封装的方法,因为只有 cpp 文件发生更改,因此编译时间要少得多。

        【讨论】:

          猜你喜欢
          • 2019-11-12
          • 2010-10-26
          • 2011-03-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-22
          • 2011-04-27
          相关资源
          最近更新 更多