C++类声明中应该放什么,不应该放什么

3

我完全不知道应该把构造函数的定义放在哪里。有时你会看到像这样的内容:

// point.h
class Point {
  Point(int x, int y) {
    x_ = x;
    y_ = y;
  }
private:
  const int x_;
  const int y_;
}

有时你会看到这样的内容:

// point.h
class Point {
  Point(int x, int y);
private:
  const int x_;
  const int y_;
}

// point.cc
Point::Point(int x, int y) {
  x_ = x;
  y_ = y;
}

即有时候像构造函数、拷贝构造函数等内容会在.h中声明,在.cc文件中实现,有时候则在头文件中定义等等。

但是在什么情况下?哪种方式是最佳实践,哪种方式不是?

5个回答

5
与某些答案相反,这两种做法存在差异。
��果将实现放在类声明中,则该方法将自动标记为inline。对于包装器之类的非常短的方法,这是一项非常有用的功能。
如果将实现放在单独的.cc文件中,则会增加可读性(如David Titarenco在他的答案中所述),并减少项目中可能存在的依赖关系。仅在方法声明中作为引用、指针或返回值出现的类在头文件中只需要前向声明。它们的实现细节在此处是不相关的,因此对于包括此头文件的每个文件也都是如此。以下是一个例子:
// test.h
class A; // forward declaration of A
// #include "decl_of_class_A.h" // not needed here, because ...

class B {
public:
  A method1();        // in these cases the forward
  void method2(A* a); // declaration of A above 
  void method3(A& a); // is completely sufficient.
};

// test.cc
#include "decl_of_class_A.h" // here we really need
                             // the complete declaration of A
                             // in order to be able to *use* it

A B::method1() { /*...*/ }
// ...

现在,如果你将test.h包含到另一个.cc文件中并且类A的声明发生了变化,则只需要重新编译test.cc而不是其他.cc文件。类B的声明没有变化。
此外,有些情况下你必须把实现放在头文件中:如果你正在编写类或函数模板或者真的想要一个函数被声明为inline
正如Omnifarious所评论的那样:你有时会看到附加的代码一定会被每个C++编译器拒绝。类的const成员必须在初始化列表中初始化。之后它们保持不变,不会为您提供默认的赋值运算符。这意味着即使使用了初始化列表,只要您没有重载赋值运算符,您永远也无法编写以下类似的内容:
Point p(10, 20);
Point q(0, 0);
q = p; // no assignment operator provided ==> error!

如果你需要恒定的坐标,应该写成const Point p(10, 20)

构造函数带有初始化列表怎么办?我是不是应该将它们放入声明中(因为构造函数的主体通常为空)? - helpermethod
1
嗯,“自动”取决于编译器。但是,将小的叶子函数内联非常理想。 - EboMike
1
@辅助方法:如果函数体为空,我通常将它们放在声明中。但是,当涉及到inline时,这实际上取决于情况。请查看C++ FAQ lite第9节“内联函数”,详细了解此主题。 - phlipsy
您没有提到初始化列表,且成员被声明为 const。这是一个严重的错误,问答者不应该接受这个答案。 - Omnifarious
@Omnifarious:他写道,他有时会看到这样的代码。但他也会这样写吗?但当然,你是对的,我会编辑我的答案,涉及这两个方面... - phlipsy

2

通常情况下,最好是将声明放在头文件中,将实现放在源文件中。尽管如此,这完全取决于作者。

只要代码易读即可 :)


不,实际上有所不同。如果您将方法的实现放入类声明中,则会隐式标记为“内联”。 - phlipsy
当然有差别,实际上有几个。在这里最佳做法(并且在工作中你会经常看到)是人们通常希望将代码放在 cpp 文件中,将头文件放在 h 文件中。养成这种习惯非常好。 - David Titarenco

1

当然,这两种定义都不是最好的方式。虽然这只是与你的问题有些关联。

答案是,如果方法非常简短并且不会使类的定义混乱,那么应该使用第一种定义样式。在类内部使用第一种样式会隐含一个inline关键字。

第二种样式也可以在头文件中声明,并且您可以使用显式inline关键字。如果该方法更复杂,或者它是模板规范的一部分,需要完整的定义才能正确地实例化模板,则这种情况更为常见。

所以,大多数情况下使用哪种定义样式主要取决于个人风格。但是,如果您使用第二种样式,即类外部声明样式,并且您的方法在头文件中声明,则您的方法应明确声明为内联或是模板类的成员。否则,链接器可能会出现重复定义警告。

最后,这是方法真正应该被定义的方式:

class Point {
  Point(int x, int y) : x_(x), y_(y) { }
private:
  const int x_;
  const int y_;
};

这是因为成员是const。它们必须在构造函数的初始化列表中初始化,而不是在构造函数体内初始化。在构造函数体内进行这些赋值是非法的,因为成员是const
无论成员是否是const,始终使用初始化列表来初始化成员是一个好习惯。这是因为如果在构造函数体中使用赋值,你最终会调用该成员的默认构造函数,然后再对其进行赋值。这可能比一开始就使用正确的值进行初始化更加昂贵,特别是当类型不是原始类型时。
此外,这也有助于防止错误,因为成员总是被初始化,而不是依赖于构造函数体内的控制流。

1

如果使用头文件,通常最好的做法是只在头文件中定义方法,并在cpp中实现它们。

您可以在类定义内部或外部定义类方法(以及构造函数/析构函数)。两者都没有问题,这只是一个风格问题。

区别只在于语法,因为如果您在类外定义方法,则必须在方法名称之前(但类型之后)添加TheClassName ::


区别不仅在于语法;在类定义中定义的成员函数自动成为“内联函数”,这意味着它的定义可以在多个编译单元中重复。 - David Thornley
您可以更清楚地表达为“(但在返回类型后面)” ,因为 TheClassName:: 也表示一种类型,但更重要的是,在C++中声称字段和方法具有默认公共访问权限是错误的。 在结构体中,默认情况下它们是公共的;在类中,默认情况下它们是私有的。 显然,使用 public:private: 可以覆盖默认值。 - Platinum Azure

1

除非有以下情况,否则应将所有函数实现放在头文件之外:

  • 因为这些函数是模板而必须这么做
  • 你希望它们被内联,并且你的编译器不支持LTCG。

一个干净的头文件对于快速了解类的功能和正确使用方法至关重要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接