在包含标题时,我有一个关于“最佳做法”的问题。
显然包含警卫保护我们不要在标头或源文件中包含多个包含,所以我的问题是你是否觉得#include包含标题或源文件中所有必需的标题是有益的,即使其中包含一个标题已经包含其中一个包括。这样做的原因是读者可以看到文件所需的一切,而不是寻找其他标题。
Ex:假设使用了防护:
// Header titled foo.h
#include "blah.h"
//....
// Header titled bar.h that needs blah.h and foo.h
#include "foo.h"
#include "blah.h" // Unnecessary, but tells reader that bar needs blah
另外,如果头文件中不需要标题,但是在相关的源文件中需要标题,那么你将它放在标题或源文件中吗?
答案 0 :(得分:10)
在你的例子中,是的,bar.h应该#include blah.h.这样,如果有人修改了foo以便它不需要等等,那么改变就不会破坏吧。
如果在foo.c中需要blah.h但在foo.h中不需要,那么在foo.h中它不应该是#included。许多其他文件可能#include foo.h,更多文件可能#include 他们。如果你在foo.h中#include blah.h,那么你就会使所有这些文件不必要地依赖于blah.h.不必要的依赖会引起很多麻烦:
答案 1 :(得分:3)
基本规则是#include
您在代码中实际使用的所有标头。所以,如果我们正在谈论:
// foo.h
#include "utilities.h"
using util::foobar;
void func() {
foobar();
}
// bar.h
#include "foo.h"
#include "utilities.h"
using util::florg;
int main() {
florg();
func();
}
bar.h
使用两次包含的标题中的工具,即使您不一定要这样,也应该#include
。另一方面,如果bar.h
不需要utilities.h
中的任何功能,那么即使foo.h
包含它,也不要#include
。
答案 2 :(得分:3)
源文件的标头应该定义代码用户需要准确使用它的接口。它应该包含他们使用界面所需的所有内容,但没有额外的内容。如果他们需要xyz.cpp提供的工具,那么用户所需要的只是#include "xyz.h"
。
'xyz.h'如何提供功能主要取决于'xyz.h'的实现者。如果它需要只能通过包含特定标头来指定的工具,那么'xyz.h'应该包含其他标头。如果它可以避免包含特定的标题(通过前向定义或任何其他清洁方式),它应该这样做。
在这个例子中,我的编码可能取决于'foo.h'标题是否在'blah.h'标题的同一项目的控制之下。如果是这样,那么我可能不会明确第二个包含;如果没有,我可以包括它。但是,上面的陈述应该强迫我说“是的,包括'foo.h'以防万一”。
在我的辩护中,我相信C ++标准允许包含任何一个C ++标题以包含其他标题 - 根据实现的要求;这可以被认为是类似的。问题是,如果你只包含'bar.h'并使用'blah.h'中的功能,那么当'bar.h'被修改因为它的代码不再需要'blah.h'时,那么用户的代码就是用于编译(意外)现在失败。
但是,如果用户直接访问'blah.h'设施,那么用户应该直接包含'blah.h'。修改后的'bar.h'中代码的接口不再需要'blah.h',所以任何只使用'bar.h'接口的代码都应该没问题。但如果代码也使用'blah.h',那么应该直接包含它。
我怀疑Demeter法也应该考虑 - 或者可以被视为影响这一点。基本上,'bar.h'应该包括使其工作所需的标题,无论是直接还是间接 - 并且'bar.h'的消费者不应该担心它。
回答最后一个问题:显然,实现所需但接口不需要的头只应包含在实现源代码中,绝对不应包含在头文件中。实现使用的与用户无关,并且编译效率和信息隐藏都要求报头仅向报头的用户公开最小必要信息。
答案 3 :(得分:0)
在C ++中的头文件中包含所有内容会导致编译时间爆炸
最好尽可能地封装和转发声明。前向声明为使用该类所需的内容提供了足够的提示。虽然在那里有标准包含是完全可以接受的(特别是模板,因为它们不能被向前声明)。
答案 4 :(得分:0)
我的头文件规则是:
在头文件中只有#include类是你班级的成员或基类 如果你的类有使用前向声明的指针或引用。
--Plop.h
#include "Base.h"
#include "Stop.h"
#include <string>
class Plat;
class Clone;
class Plop: public Base
{
int x;
Stop stop;
Plat& plat;
Clone* clone;
std::string name;
};
Caviat:如果您在头文件(示例模板)中定义成员,那么您可能需要包含Plat和Clone(但只有在绝对必要时才这样做)。
在源文件中将头文件从最特定的顺序放到最不具体的顺序 但不包含您不明确需要的任何内容。
因此,在这种情况下,您将参与:
这里的论点是,如果Clone.h需要map(并且Plop需要map)并且你将C ++头文件放在列表顶部附近,那么你隐藏了Clone.h需要map的事实,因此你可能不会添加它在Clone.h中。
始终使用标题保护
#ifndef <NAMESPACE1>_<NAMESPACE2>_<CLASSNAME>_H
#define <NAMESPACE1>_<NAMESPACE2>_<CLASSNAME>_H
// Stuff
#endif
PS:我不建议使用多个嵌套命名空间。我只是在展示如果我这样做我该怎么做。我正常地将所有内容(除了main)放在命名空间中。嵌套取决于具体情况。
避免使用声明 除了我正在研究的课程的当前范围:
-- Stop.h
#ifndef THORSANVIL_XXXXX_STOP_H
#define THORSANVIL_XXXXX_STOP_H
namespace ThorsAnvil
{
namespace XXXXX
{
class Stop
{
};
} // end namespace XXXX
} // end namespace ThorsAnvil
#endif
-- Stop.cpp
#include "Stop.h"
using namespace ThorsAnvil:XXXXX;
答案 5 :(得分:0)
我的评论可能不是您问题的直接答案,但很有用。
IOD / IOP鼓励尽可能减少INTERFACE标头中的标头,这样做的主要好处是:
每个IOD / IOP,接口只能放在.h / .hxx标头中。在.c / .cpp中包含标题。