2011-03-01 78 views
3

我完全困惑在哪里把例如构造函数的定义。有时候你看到这样的事情C++什么放在一个类声明,什么不是

// 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

与一些答案相反,这两种做法存在差异。

如果将实现放入类声明中,该方法将自动标记为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)

+0

初始化列表的构造函数呢?我应该把它们放入声明中(因为构造函数的主体通常是空的?)。 – helpermethod 2011-03-01 19:55:34

+1

那么,“自动”取决于编译器。但是,是的,内嵌小叶函数是非常理想的。 – EboMike 2011-03-01 19:55:35

+1

@Helper方法:如果函数体是空的,我通常会将它们放入声明中。但是当涉及到“内联”时,这取决于它。查看C++ FAQ lite,第9节“内联函数”以获得对该主题的详细描述。 – phlipsy 2011-03-01 20:01:02

1

如果使用头文件,通常只需要在头文件中定义方法并在cpp中实现它们。

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

不同之处在于语法,因为如果您在类的外部定义方法,则必须在方法名称(但类型之后)之前添加TheClassName::

+0

区别不在于语法;在类定义中定义的成员函数自动为'inline',这意味着它的定义可以在多个编译单元中重复。 – 2011-03-01 19:42:21

+0

-1:你可以通过说“(但是在返回类型之后)”使它更清楚,因为TheClassName ::'也表示一个类型,但更重要的是,在C++中默认情况下字段和方法是公共的声明是错误的。在结构上,它们默认是公开的;在课程中它们默认是私密的。显然,使用'public:'和'private:'覆盖默认值。 – 2011-03-01 19:43:27

2

通常最好在头文件和实现中保存声明。这就是说,完全取决于作者。

只要它是可读的:)

+0

其实没有什么区别。如果将方法的实现放入类声明中,它将被隐式标记为“inline”。 – phlipsy 2011-03-01 19:54:18

+0

当然有区别。实际上有几个。这里的最佳实践(以及在工作时你会经常看到)是人们通常希望在'cpp'文件中的代码和在'h'文件中的头文件。养成这样做的习惯是很好的。 – 2011-03-01 20:02:49

1

你应该保持头文件之外的所有功能的实现,除非

  • 你要因为他们的模板
  • 你希望他们被内联,并且您的编译器不执行LTCG。

一个干净的头文件对于快速了解一个类是什么以及应该如何使用它非常有用。

1

当然,这两个定义都不是最好的方法。尽管这只与你的问题有关。

答案是,如果方法非常短并且不会混淆类定义,则应该使用第一种定义样式。第一种风格在课堂内部,其作用是在定义之前加入隐含的关键字inline

第二种样式也可以在头文件中声明,您可以使用明确的inline关键字。如果方法更复杂,或者它是模板规范的一部分,需要使用完整定义以便可以使用当前编译器正确实例化模板,则这更为常见。

所以,大多数情况下,这是一个风格问题,您使用的是哪种定义。虽然如果使用第二种样式,即类声明样式外部的方法,并且您的方法在头文件中声明,则应该以明确的内联方式声明您的方法或者声明方法是模板类的成员。否则,链接器可能最终会给你重复的定义警告。

最后,这里是如何的方法应该被定义为:

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

这是因为成员是const。它们必须在构造函数初始化器列表中初始化,而不是在构造函数体内。在构造函数体中,这些分配是非法的,因为成员是const

不管是否const,始终使用初始化列表来初始化成员是个好习惯。这是因为如果您在构造函数体中使用赋值,最终会调用成员的默认构造函数,然后为其分配一些内容。这可能比仅仅用适当的值初始化它更昂贵,特别是如果该类型不是基本类型。

它还有助于防止错误,因为您的成员始终是初始化的,而不是初始化依赖于构造函数体内的控制流。