我使用C++的个人风格总是把类声明放在一个包含文件中,并在.cpp文件中定义,非常类似于Loki对C++头文件,代码分离的回答.不可否认,我喜欢这种风格的部分原因可能与我花费编码Modula-2和Ada的所有年份有关,两者都有与规范文件和正文文件类似的方案.
我有一个同事,比我更了解C++,他坚持认为所有的C++声明应尽可能在头文件中包含定义.他并不是说这是一种有效的替代风格,甚至是一种稍微好一点的风格,而是这是每个人现在都用于C++的新普遍接受的风格.
我不像以前那样柔软,所以我并不急于拼抢他的这个潮流,直到我看到更多的人和他在一起.那个成语真的有多常见?
只是给出答案的一些结构:它现在是The Way,非常常见,有点普遍,不常见,还是出错?
您的同事是错误的,常见的方法是始终将代码放入.cpp文件(或您喜欢的任何扩展名)和标题中的声明.
将代码放入标题中偶尔会有一些优点,这可以让编译器更加巧妙地进行内联.但与此同时,它可能会破坏您的编译时间,因为每次编译时都必须处理所有代码.
最后,当所有代码都是标题时,拥有循环对象关系(有时是期望的)通常很烦人.
最重要的是,你是对的,他错了.
编辑:我一直在考虑你的问题.有一种情况他说的是真的.模板.许多较新的"现代"库(例如boost)大量使用模板,并且通常只是"标题".但是,这只应在处理模板时完成,因为它是处理模板时唯一的方法.
编辑:有些人想要更多的澄清,这里有一些关于编写"仅标题"代码的缺点的想法:
如果你四处搜索,你会看到很多人试图找到一种方法来减少处理boost时的编译时间.例如:如何使用Boost Asio减少编译时间,Boost Asio看到包含boost的单个1K文件的14s编译.14s可能似乎没有"爆炸",但它肯定比典型的更长,并且可以很快加起来.在处理大型项目时.仅头文件库确实以一种非常可测量的方式影响编译时间.我们只是容忍它,因为助推是如此有用.
另外,有很多东西只能在头文件中完成(甚至boost也需要链接到某些部分的库,如线程,文件系统等).一个主要的例子是你不能在头文件库中有简单的全局对象(除非你诉诸于单身的憎恶),因为你会遇到多个定义错误.注意: C++ 17的内联变量将使这个特定的例子在将来可行.
最后一点,当使用boost作为仅标题代码的示例时,通常会遗漏一个巨大的细节.
Boost是库,而不是用户级代码.所以它不会经常改变.在用户代码中,如果将所有内容放在标题中,每次稍微更改都会导致您必须重新编译整个项目.这是一个巨大的浪费时间(对于不会从编译变为编译的库不是这种情况).当你在header/source和更好的方法之间进行分割时,使用forward声明来减少包含,你可以在一天内添加时节省数小时的重新编译.
C++编码人员同意The Way,羔羊将与狮子躺下,巴勒斯坦人将拥抱以色列人,猫狗将被允许结婚.
此时.h和.cpp文件之间的分离大多是任意的,这是编译器优化的遗迹.在我看来,声明属于标题,定义属于实现文件.但是,这只是习惯,而不是宗教.
标头中的代码通常是一个坏主意,因为它会在您更改实际代码而不是声明时强制重新编译包含标头的所有文件.它还会减慢编译速度,因为您需要解析包含标头的每个文件中的代码.
在头文件中包含代码的一个原因是,通常需要关键字内联才能正常工作以及使用在其他cpp文件中实例化的模板.
什么可能告诉你同事是一个概念,大多数C++代码应该被模板化,以允许最大的可用性.如果它是模板化的,那么一切都需要在头文件中,以便客户端代码可以看到它并实例化它.如果它对Boost和STL来说足够好,那对我们来说已经足够了.
我不同意这种观点,但它可能是它的来源.
我认为你的同事很聪明,你也是对的.
我发现将所有内容放入标题中的有用信息是:
无需编写和同步标头和来源.
结构很简单,并且没有循环依赖性迫使编码器形成"更好"的结构.
便携,易于嵌入到新项目中.
我同意编译时间问题,但我认为我们应该注意到:
源文件的更改很可能会更改头文件,从而导致整个项目再次重新编译.
编译速度比以前快得多.如果你有一个长时间高频率的项目,它可能表明你的项目设计有缺陷.将任务分成不同的项目和模块可以避免这个问题.
最后,我只是想以你个人的观点支持你的同事.
通常我会将简单的成员函数放入头文件中,以允许它们被内联.但要将整个代码放在那里,只是为了与模板保持一致?那简直就是坚果.
请记住:愚蠢的一致性是小脑袋的大人物.
正如Tuomas所说,你的标题应该是最小的.为了完成,我会稍微扩展一下.
我个人在我的C++
项目中使用了4种类型的文件:
上市:
转发标头:如果是模板等,此文件将获得将出现在标头中的转发声明.
标题:此文件包含转发标头(如果有),并声明我希望公开的所有内容(并定义类...)
私人的:
私有标头:此文件是为实现保留的标头,它包含标头并声明辅助函数/结构(例如对于Pimpl或谓词).如果没必要,请跳过.
源文件:它包括私有头(如果没有私有头,则包含头)并定义所有内容(非模板...)
此外,我将此与另一条规则相结合:不要定义您可以向前声明的内容.虽然我当然合情合理(使用Pimpl到处都很麻烦).
这意味着#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; templateclass MyClassT; // example.hpp #include "project/example_fwd.hpp" // Those can't really be skipped #include #include #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 mImpl; // I won't describe pimpl here :p }; template class MyClassT: public MyClass {}; } // namespace project // example_impl.hpp (not visible to clients) #include "project/example.hpp" #include "project/bar.hpp" template void check(MyClass const& c) { } // example.cpp #include "example_impl.hpp" // MyClass definition
这里的救生员是大多数时候前向头是无用的:只有在typedef
或者情况下是必需template
的实现头;)
为了增加乐趣,您可以添加.ipp
包含模板实现(包含在内.hpp
)的文件,同时.hpp
包含界面.
除了模板化的代码(取决于项目,这可能是大多数或少数文件),有正常的代码,这里最好分隔声明和定义.在需要时提供前向声明 - 这可能会影响编译时间.
通常,在编写新类时,我会将所有代码放在类中,因此我不必查看其他文件.在一切正常后,我将方法的主体分解为cpp文件,将原型保留在hpp文件中.