我有一个自定义Stack实现的例子。据我所知,.h
文件仅包含声明,而cpp
文件应仅包含< EM>实现。我在cplusplus.com/stack_example上找到了一个自定义堆栈的示例,它看起来如下所示。
Stack.h文件
#ifndef _STACK_H_
#define _STACK_H_
#include <iostream>
#include "Exception.h"
template <class T>
class Stack {
public:
Stack():top(0) {
std::cout << "In Stack constructor" << std::endl;
}
~Stack() {
std::cout << "In Stack destructor" << std::endl;
while ( !isEmpty() ) {
pop();
}
isEmpty();
}
void push (const T& object);
T pop();
const T& topElement();
bool isEmpty();
private:
struct StackNode { // linked list node
T data; // data at this node
StackNode *next; // next node in list
// StackNode constructor initializes both fields
StackNode(const T& newData, StackNode *nextNode)
: data(newData), next(nextNode) {}
};
// My Stack should not allow copy of entire stack
Stack(const Stack& lhs) {}
// My Stack should not allow assignment of one stack to another
Stack& operator=(const Stack& rhs) {}
StackNode *top; // top of stack
};
现在我有疑问。这个.h
文件显然会显示一些实现细节。构造函数和析构函数都在.h
文件中实现。根据我的理解,这些应该在.cpp
文件中实现。此外,struct StackNode
文件中还有.h
已实施。甚至可以在.cpp
文件中实现,只在头文件中声明它?作为一般规则,如果那些在.cpp
实施文件中,会不会更好?编写这个东西以便遵循C ++规则的最佳方法是什么?
答案 0 :(得分:4)
头文件与源文件没有根本区别。
原则上,标头可以包含源文件可以使用的相同代码结构。按照惯例,唯一能区分标题和源文件的标题是标题应该是#include
d由其他文件组成,我们通常不会对源文件进行处理(尽管我们可以,如果我们感觉到的话)冒险)。
现在重要的是,如果文件通过多个源文件获得#include
d,会发生什么。请注意,这基本上相当于将相同的代码复制粘贴到多个.cpp文件。在这种情况下,某些事情可能会让你陷入困境。
特别是,如果最终在两个不同的源文件中为同一个符号定义了两个定义,那么您的程序格式不正确(根据the one-definition rule),链接器可能会拒绝链接您的程序。
对于不同的源文件,这通常不是问题,除非您不小心在不同的上下文中重用了一个名称。一旦头文件进入图片(这只是复制粘贴代码),事情开始变得不同。您仍然可以在头文件中放置一个定义,但如果该头文件被多个源文件拉入,那么您自己就会违反一个定义规则。
因此,惯例是只将东西放入可以跨多个源文件复制的头文件中。这包括声明,内联函数定义和模板。
这里的关键实现可能是头文件是一个相当古老的工具,用于在源文件之间共享代码。因此,他们在很大程度上依赖于用户足够聪明而不会搞砸事情。
答案 1 :(得分:3)
必须在头文件中没有标准规则,标准库标头例外。我的意思是,从理论上讲,我们可以完全避免头文件以及在.cpp
文件中复制和粘贴声明。
那就是说,这更多的是常识和经验问题。您可以根据自己的意愿和需要将内容放入标题或.cpp
中。我们可以列出一些用例:
模板(如您的示例中)声明和实施通常进入标头。有关详细信息,请参阅this thread。
您执行当您认为/希望它们将用于多个翻译单元时,将函数声明放在标题中
当您不希望/认为它们在其他翻译单元中使用时,您不将函数声明放在标题中
如果您希望函数为inline
d并希望该函数在不同的翻译单元中使用,那么您将其定义放在标题中(前面有inline
关键字if它是非会员功能)
当您认为/希望访问该类型的对象时,您执行在标题中放置一个类声明(也称为前向声明) *来自不同的翻译单位
当您认为/希望仅在一个翻译单元中访问该类时,您不在标题中放置一个类声明
当您认为/希望创建该类型的对象时, 将类定义(即整个类接口)放入标题中在不同的翻译单位
当您希望仅在一个翻译单元中创建该类型的对象时不将类定义放在标题中
如果你想定义一个全局变量,你很难将定义放在头文件中(除非你想把这个标题只包含在一个翻译单元中)
如果您正在开发一个库,您将把要提供给用户的那些函数和类的声明放入头文件中
如果您正在开发一个库,您将找到一种方法将那些您不想公开的实现细节放入.cpp
文件中(如@Joachim Pileborg建议的那样,请参阅{{3} })
您通常不希望在标头中使用声明或使用指令来放置,因为它们会污染这些翻译将#include
标题
如果可能,您不希望#include
其他标题进入您的标题;你肯定做更愿意转发声明你的程序编译所需要的东西
最后,粗略地说,你在标题中放的东西越少,你的文件编译得越快;并且,让我说明显而已, 做 希望您的文件快速编译!
备注强>
上面我主要讨论了类和函数,但一般来说,这些规则对枚举和typedef
声明都有效。
标题中缺少标题中的成员函数定义,因为它是函数定义里面或外部类定义的问题,而不是.h
vs .cpp
个文件
* 访问我的意思是通过指针或参考使用,与创建相反
答案 2 :(得分:3)
头文件究竟是什么?
根据您的学习内容和设计,将尽可能少的内容放入标题中。标头是使用实现细节所需的最少的东西。更简单的标题通常也会返回更简单易用的代码,与大标题相比,它们的编译时间更短。
当然,在C ++标题中与源文件没有什么不同,依靠您的技能将源代码制作成不同的文件,以使您的项目更易于使用和理解。
有些情况下你被迫把东西放在标题中(在例子中使用模板),如果你没有被迫这样做,最好将东西放入标题和放大器中。源文件。
编译器将转换为二进制ANY源文件(header或cpp),唯一的必要条件是至少有一些“可编译”代码(函数/方法的主体,即使空)。
与众不同的是程序员:
如果你将一个函数的签名放在一个无法编译的头文件中,你还必须编译该函数的主体,以便用它实际生成一个可运行的程序(如果你没有一个正文)某处你会得到链接器错误)
<强> Header.h 强>
#pragma once
// by including this file you are actually promising that
// you will later add also the body of this function.
int function(int a);
<强> Source.cpp 强>
#include <liba>
#include <libb>
#include "Header.h"
// by compiling this file AND linking into your binaries
// you satisfy the promise by providing the real function
int function(int a){
return liba::fun(a)+libb::fun(b);
}
这有用吗?是。您包含的每个文件都会增加编译时间并为代码添加依赖项(如果您更改了标题,则必须重新编译程序,并且您可能还会遇到编译错误,但如果您将内容保存到2个文件中,则可以使用更简单的标题和您可以在函数内部更改次要内容,而无需重新编译大量文件:在大型项目中,编译时间是一个真正的问题。)
以下2个示例是等效的(生成相同的程序集):
<强>的main.cpp 强>
#include "Header.h"
int main(){
return function(3);
}
您只包含1个文件
<强> main2.cpp 强>
#include "Source.cpp" //note source.cpp here!
int main(){
return function(3);
}
您包含Source.cpp
,Header.h
,liba
,libb
(可能更多)文件,结果编译时间缩短了4倍。
当然,标题还允许您通过避免重复定义来进行更简单的生活编程。
双重包含
#include "Header.h"
#include "Header.h" //bad, but allowed!
int main(){
return function(3);
}
双重包含
#include "Source.cpp"
#include "Source.cpp" //error! redefinition (not compile)
int main(){
return function(3);
}
来自不同地方的双重包含
file1.cpp
#include "Source.cpp" // Bad! include Header.h
file2.cpp
#include "Source.cpp" //error symbol already define!