将类代码分为头文件和cpp文件


169

我对如何将简单类的实现和声明代码分成新的标头和cpp文件感到困惑。例如,如何分隔下一个类的代码?

class A2DD
{
  private:
  int gx;
  int gy;

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

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

12
仅有几点评论:构造函数应始终使用初始化列表,而不是在主体中设置成员。有关简单明了的说明,请参见:codeguru.com/forum/showthread.php?t=464084至少在大多数地方,习惯上也将公共字段放在顶部。它不会有任何影响,但是由于公共字段是您课程的文档,因此将其放在顶部是有意义的。
martiert 2012年

2
@martiert有public:顶部的成员可能会影响很多,如果用户根据他们对这个建议感动-但已经订购成员之间的依赖关系,并还没有意识到成员在其声明;-)的顺序初始化
underscore_d

1
@underscore_d是正确的。但是话又说回来,我们都将警告编译为错误以及我们能想到的所有警告,对吗?那至少会告诉您您正在
搞砸

@martiert好点,有点忘了会生成警告-如果大多数人只阅读警告:-)我使用它们并尝试全部编码。有一些是不可避免的-所以我说“感谢警告,但我知道我在做什么!” -但最好固定大多数,以免日后造成混乱。
underscore_d

将公共领域放在顶部只是一种风格,不幸的是,我认为太多的领域采用了这种风格。另外,您需要牢记@martiert提到的一些事情。
瓦西里斯

Answers:


232

类声明进入头文件。添加#ifndef包含防护很重要,如果您在MS平台上,也可以使用#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;
}

52
请记住,如果要进行模板编程,则必须将所有内容保存在.h文件中,以便编译器在编译时实例化正确的代码。
linello'3

2
#ifndef标头中有东西吗?
Ferenc Deak 2012年

4
因此,这意味着包含头文件的所有文件都将“查看”私有成员。例如,如果要发布一个lib及其标题,则必须显示该类的私有成员?
Gauthier

1
不,有一个很棒的私有实现习惯用法:en.wikipedia.org/wiki/Opaque_pointer您可以使用它来隐藏实现细节。
Ferenc Deak 2013年

3
小nitpick的写法是:“类声明进入头文件”。这确实是一个声明,但它也是一个定义,但是由于后者包括前者,所以我宁愿说类定义进入了头文件。在翻译单元中,您具有成员函数的定义,而不是类的定义。我同意,这可能值得小修改吗?
lubgr

17

通常,.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;
}

7

重要的是要向读者指出,当您以更广泛的方式研究该主题时,绊倒这个问题时,在仅要将项目拆分为文件的情况下,不需要接受的答案即可。仅在需要单个类的多个实现时才需要它。如果每个类的实现是一个,则每个仅一个头文件就足够了。

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

#ifndef MYHEADER_H
#define MYHEADER_H

//Class goes here, full declaration AND implementation

#endif

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

PS。一旦您意识到C / C ++是'哑巴',并且#include只是说“在此处转储此文本”的一种方式,该主题将变得更加清晰。


您可以通过将“拆分”文件放入中来实现此功能.cpp,还是仅对.h这种代码组织方法真正“好”?
Benny Jobigan '19

1
我认为有些项目将头文件和(单个)实现文件分开,以便它们可以轻松地分发头文件,而无需透露实现的源代码。
卡尔·G

我为您指出这一点感到非常高兴,因为我最初是在C ++上学习的,然后在很多年前切换到C#,最近又又做了很多C ++,但我忘记了拆分文件是多么乏味和烦人的事,只是开始将所有内容放入标头。我到处都是寻找有充分理由不这样做的人。@CarlG有一个很好的观点,但是除了这种情况之外,我认为所有这些都是内联的。
彼得·摩尔

6

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

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;
  }

想法是将所有函数签名和成员保留在头文件中。
这将使其他项目文件无需了解实现即可查看类的外观。

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


4

您将声明保留在头文件中:

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()定义在标头中)。这很有用,因为它使编译器有更好的机会进行内联。但这也意味着更改实现(如果保留在标头中)可能会触发所有其他包含标头的文件的重建。

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


1
将私有成员和函数放在头文件中不被视为泄漏实现细节吗?
杰森

1
@Jason,有点。这些是必要的实施细节。例如,我必须知道一个类将在堆栈上占用多少空间。其他编译单元不需要功能实现。
保罗·德雷珀

1

通常,您只在头文件中放置声明和真正短的内联函数:

例如:

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_;
 };

0

我不会过多地引用您的示例,因为它对于一般性回答来说非常简单(例如,它不包含模板化函数,这些函数迫使您在标头上实现它们),根据我的经验,Pimpl是成语

当您获得更快的编译时间和语法糖时,它具有很多好处:

class->member 代替 class.member

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.