我个人使用c++的风格总是把类声明放在包含文件中,定义放在。cpp文件中,这很像Loki对c++头文件的回答,代码分离。不可否认,我喜欢这种风格的部分原因可能与我花了这么多年来编写Modula-2和Ada有关,这两者都有类似的规范文件和主体文件方案。
我有一个同事,他比我更了解c++,他坚持认为所有c++声明都应该尽可能地在头文件中包含定义。他并不是说这是一种有效的替代风格,甚至不是一种稍微更好的风格,而是说这是一种新的普遍接受的风格,现在每个人都在使用c++。
我不像以前那么灵活了,所以在我看到更多的人和他在一起之前,我并不急于加入他的行列。那么这个习语到底有多普遍呢?
只是给答案一些结构:现在的方式™,非常常见,有点常见,不常见,还是疯狂?
正如图马斯所说,你的头球应该是最小的。为了完整,我将展开一点。
我个人在我的c++项目中使用4种类型的文件:
Public:
Forwarding header: in case of templates etc, this file get the forwarding declarations that will appear in the header.
Header: this file includes the forwarding header, if any, and declare everything that I wish to be public (and defines the classes...)
Private:
Private header: this file is a header reserved for implementation, it includes the header and declares the helper functions / structures (for Pimpl for example or predicates). Skip if unnecessary.
Source file: it includes the private header (or header if no private header) and defines everything (non-template...)
此外,我还附带了另一条规则:不要定义可以转发声明的内容。当然,我在那里是合理的(到处使用皮impl是相当麻烦的)。
这意味着我更喜欢在头文件中使用前向声明,而不是使用#include指令。
最后,我还使用了一个可见性规则:我尽可能地限制符号的作用域,这样它们就不会污染外部作用域。
总的来说:
// example_fwd.hpp
// Here necessary to forward declare the template class,
// you don't want people to declare them in case you wish to add
// another template symbol (with a default) later on
class MyClass;
template <class T> class MyClassT;
// example.hpp
#include "project/example_fwd.hpp"
// Those can't really be skipped
#include <string>
#include <vector>
#include "project/pimpl.hpp"
// Those can be forward declared easily
#include "project/foo_fwd.hpp"
namespace project { class Bar; }
namespace project
{
class MyClass
{
public:
struct Color // Limiting scope of enum
{
enum type { Red, Orange, Green };
};
typedef Color::type Color_t;
public:
MyClass(); // because of pimpl, I need to define the constructor
private:
struct Impl;
pimpl<Impl> mImpl; // I won't describe pimpl here :p
};
template <class T> class MyClassT: public MyClass {};
} // namespace project
// example_impl.hpp (not visible to clients)
#include "project/example.hpp"
#include "project/bar.hpp"
template <class T> void check(MyClass<T> const& c) { }
// example.cpp
#include "example_impl.hpp"
// MyClass definition
这里的救星是大多数时候forward头是无用的:只有在typedef或模板的情况下才需要,实现头也是;)
你的同事错了,通常的方法是把代码放在。cpp文件(或任何你喜欢的扩展名)中,并在头文件中声明。
将代码放在头文件中偶尔会有一些好处,这可以让编译器更聪明地进行内联。但与此同时,它会破坏编译时间,因为所有代码在每次被编译器包含时都必须被处理。
最后,当所有的代码都是头部时,循环对象关系(有时是需要的)通常是令人讨厌的。
总之,你是对的,他是错的。
编辑:我一直在思考你的问题。在一种情况下,他说的是真的。模板。许多较新的“现代”库,如boost,大量使用模板,通常是“头文件”。但是,只有在处理模板时才应该这样做,因为这是处理模板时的唯一方法。
编辑:有些人想要更多的澄清,这里有一些关于写“只有头”代码的缺点的想法:
If you search around, you will see quite a lot of people trying to find a way to reduce compile times when dealing with boost. For example: How to reduce compilation times with Boost Asio, which is seeing a 14s compile of a single 1K file with boost included. 14s may not seem to be "exploding", but it is certainly a lot longer than typical and can add up quite quickly when dealing with a large project. Header only libraries do affect compile times in a quite measurable way. We just tolerate it because boost is so useful.
此外,还有许多事情不能仅在头文件中完成(甚至boost也有一些库,您需要链接到某些部分,如线程、文件系统等)。一个主要的例子是你不能在只有头的库中有简单的全局对象(除非你求助于讨厌的单例),因为你会遇到多个定义错误。注意:c++ 17的内联变量将使这个特殊的例子在未来可行。
最后一点,当使用boost作为仅头代码的示例时,经常会忽略一个巨大的细节。
Boost是库,而不是用户级代码。所以它不会经常变化。在用户代码中,如果你把所有东西都放在头文件中,每一个小的改变都会导致你不得不重新编译整个项目。这是对时间的巨大浪费(对于那些在不同的编译器之间不进行更改的库来说,情况并非如此)。当你在头文件/源文件之间拆分时,更好的是,使用前向声明来减少包含,你可以在一天内节省数小时的重新编译时间。