【问题标题】:Separating class code into a header and cpp file将类代码分离为头文件和 cpp 文件
【发布时间】:2021-04-03 16:17:55
【问题描述】:

我很困惑如何将一个简单类的实现和声明代码分离到一个新的头文件和 cpp 文件中。比如下面这个类的代码怎么分开?

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y)
  {
    gx = x;
    gy = y;
  }

  int getSum()
  {
    return gx + gy;
  }
};

【问题讨论】:

  • 只是几个 cmets:构造函数应该始终使用初始化列表,而不是在主体中设置成员。对于一个好的和简单的解释,请参阅:codeguru.com/forum/showthread.php?t=464084 至少在大多数地方,公共字段也习惯在顶部。它不会影响任何事情,但由于公共字段是您的课程的文档,因此将其放在顶部是有意义的。
  • @martiert 顶部有public: 成员可能会影响很多,如果用户根据此建议移动它们 - 但有订购成员之间的依赖关系,并且还不知道成员按其声明的顺序初始化;-)
  • @underscore_d 这是真的。但是话又说回来,我们都在编译时将警告作为错误以及我们能想到的所有警告,对吧?这至少会告诉你你把这件事搞砸了,但是,是的,人们使用很少的警告,只是忽略它们:(
  • @martiert 好点,有点忘了生成警告 - 如果大多数人只阅读警告:-) 我使用它们并尝试将它们全部编码。有些是不可避免的——所以我说“谢谢你的警告,但我知道我在做什么!” - 但大多数最好修复以避免以后混淆。
  • 在顶部设置公共字段只是一种风格,在我看来,不幸的是,太多人采用了这种风格。此外,您需要记住@martiert 提到的一些事项。

标签: c++ oop class


【解决方案1】:

类声明进入头文件。添加#ifndef 包含警卫很重要。大多数编译器现在也支持#pragma once。我也省略了私有,默认情况下 C++ 类成员是私有的。

// A2DD.h
#ifndef A2DD_H
#define A2DD_H

class A2DD
{
  int gx;
  int gy;

public:
  A2DD(int x,int y);
  int getSum();

};

#endif

并且实现在 CPP 文件中:

// A2DD.cpp
#include "A2DD.h"

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

 

【讨论】:

  • 请记住,如果您在进行模板编程,则必须将所有内容保存在 .h 文件中,以便编译器在编译时实例化正确的代码。
  • 标题中有#ifndef 的东西吗?
  • 所以这意味着所有包含你的头文件的文件都将“看到”私有成员。例如,如果您想发布一个库及其标头,您必须显示该类的私有成员?
  • 不,有一个很棒的私有实现成语:en.wikipedia.org/wiki/Opaque_pointer你可以用它来隐藏实现细节。
  • 次要的措辞:“类声明进入头文件”。这确实是一个声明,但它也是一个定义,但由于后者包含前者,我宁愿说类定义进入头文件。在翻译单元中,您有成员函数的定义,而不是类的定义。我同意,这可能值得稍作修改?
【解决方案2】:

一般来说,您的 .h 包含类定义,即您的所有数据和所有方法声明。在你的情况下是这样的:

A2DD.h:

class A2DD
{
  private:
  int gx;
  int gy;

  public:
  A2DD(int x,int y);    
  int getSum();
};

然后您的 .cpp 包含如下方法的实现:

A2DD.cpp:

A2DD::A2DD(int x,int y)
{
  gx = x;
  gy = y;
}

int A2DD::getSum()
{
  return gx + gy;
}

【讨论】:

    【解决方案3】:

    重要的是要向读者指出,在以更广泛的方式研究该主题时偶然发现此问题,如果您只想将项目拆分为文件,则不需要接受答案的过程。仅当您需要单个类的多个实现时才需要它。如果每个类的实现是一个,那么每个类只需要一个头文件就足够了。

    因此,从接受的答案示例中,只需要这部分:

    #ifndef MYHEADER_H
    #define MYHEADER_H
    
    //Class goes here, full declaration AND implementation
    
    #endif
    

    #ifndef 等预处理器定义允许它被多次使用。

    附言。一旦您意识到 C/C++ 是“愚蠢的”并且#include 只是一种说“在此位置转储此文本”的方式,该主题就会变得更加清晰。

    【讨论】:

    • 您可以通过将“拆分”文件放在.cpp 中来做到这一点,还是只有.h 对于这种代码组织方法真的“好”?
    • 我认为有些项目将头文件和(单个)实现文件分开,以便他们可以轻松分发头文件而不会泄露实现的源代码。
    • 我很高兴你指出了这一点,因为我最初学习 C++,然后在多年前切换到 C#,最近又做了很多 C++,我忘记了拆分文件是多么乏味和烦人up 是并且刚刚开始将所有内容放在标题中。当我发现这个时,我正在四处寻找任何给出充分理由不这样做的人。 @CarlG 有一个很好的观点,但除了那种情况,我认为内联是要走的路。
    【解决方案4】:

    基本上是函数声明/定义的修改语法:

    a2dd.h

    class A2DD
    {
    private:
      int gx;
      int gy;
    
    public:
      A2DD(int x,int y);
    
      int getSum();
    };
    

    a2dd.cpp

    A2DD::A2DD(int x,int y)
    {
      gx = x;
      gy = y;
    }
    
    int A2DD::getSum()
    {
      return gx + gy;
    }
    

    【讨论】:

      【解决方案5】:

      A2DD.h

      class A2DD
      {
        private:
        int gx;
        int gy;
      
        public:
        A2DD(int x,int y);
      
        int getSum();
      };
      

      A2DD.cpp

        A2DD::A2DD(int x,int y)
        {
          gx = x;
          gy = y;
        }
      
        int A2DD::getSum()
        {
          return gx + gy;
        }
      

      这个想法是将所有函数签名和成员保留在头文件中。
      这将允许其他项目文件查看类的外观,而无需知道实现。

      除此之外,您还可以在实现中包含其他头文件而不是头文件。这很重要,因为任何包含在头文件中的头都将包含(继承)在包含您的头文件的任何其他文件中。

      【讨论】:

        【解决方案6】:

        您将声明留在头文件中:

        class A2DD
        {
          private:
          int gx;
          int gy;
        
          public:
            A2DD(int x,int y); // leave the declarations here
            int getSum();
        };
        

        并将定义放入实现文件中。

        A2DD::A2DD(int x,int y) // prefix the definitions with the class name
        {
          gx = x;
          gy = y;
        }
        
        int A2DD::getSum()
        {
          return gx + gy;
        }
        

        您可以将两者混合使用(例如,在标题中保留 getSum() 定义)。这很有用,因为它为编译器提供了更好的内联机会。但这也意味着更改实现(如果留在标头中)可能会触发包含标头的所有其他文件的重建。

        请注意,对于模板,您需要将其全部保留在标题中。

        【讨论】:

        • 将私有成员和函数放在头文件中不算泄露实现细节?
        • @Jason,有点。这些是必要的实现细节。例如,我必须知道一个类将在堆栈上消耗多少空间。其他编译单元不需要函数实现。
        【解决方案7】:

        通常你只在头文件中放置声明和非常短的内联函数:

        例如:

        class A {
         public:
          A(); // only declaration in the .h unless only a short initialization list is used.
        
          inline int GetA() const {
            return a_;
          }
        
          void DoSomethingCoplex(); // only declaration
          private:
           int a_;
         };
        

        【讨论】:

          【解决方案8】:

          我不会参考您的示例,因为它对于一般答案非常简单(例如,它不包含模板函数,这会迫使您在标题上实现它们),我遵循的经验法则是pimpl idiom

          它有很多好处,因为您可以获得更快的编译时间和语法糖:

          class->member 而不是class.member

          唯一的缺点是您支付的额外指针。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-09-15
            • 1970-01-01
            • 2021-06-02
            • 1970-01-01
            相关资源
            最近更新 更多