C ++代码在头文件中
我个人的C ++风格总是把类声明放在一个包含文件中,并且定义在一个.cpp文件中,非常类似于Loki对C ++头文件,代码分离的回答 。 不可否认的是,我喜欢这种风格的部分原因可能与我花费在编码Modula-2和Ada上的所有年份有关,它们都与规范文件和主体文件有相似的scheme。
我有一个同事,比C ++更懂C ++,他坚持所有的C ++声明都应该尽可能在头文件中包含定义。 他并不是说这是一种有效的替代风格,甚至是稍微好一些的风格,但是这是每个人现在都在使用的新的普遍接受的风格。
我不像以前那么灵活,所以我并不急于拼命追赶他,直到我看到更多的人在他身边。 那这个成语真的有多常见?
只是给一些结构的答案:是现在的方式 ,很常见,有点常见,不寻常或错误疯狂?
你的同事是错误的,常见的方式是,总是把代码放入.cpp文件(或者你喜欢的任何扩展名)和头文件中的声明。
在代docker中偶尔会有一些优点,这可以让编译器更聪明地内联。 但是与此同时,它可能会破坏您的编译时间,因为编译器每次都要处理所有代码。
最后,当所有的代码都是标题时,通常会有令人讨厌的循环对象关系(有时候是需要的)。
底线,你是对的,他是错的。
编辑:我一直在想你的问题。 有一种情况是他所说的是真实的。 模板。 许多较新的“现代”库(如boost)大量使用模板,而且通常只是“标题”。 但是,这只能在处理模板时才能完成,因为这是处理模板的唯一方法。
编辑:有些人想要更多的澄清,这里有一些想法写下“头只”代码的缺点:
如果你四处search,你会看到很多人试图find一种方法来减less编译时间在处理提升。 例如: 如何减less编译时间与升压阿西奥 ,这是看到一个单一的1K文件与包括提升14s编译。 14可能不是“爆炸”,但它肯定比典型的要长很多,并且可以很快加起来。 在处理大型项目时。 只有头文件的库确实会影响编译时间。 我们只是容忍它,因为提升是如此有用。
另外,还有很多东西是不能在头文件中完成的(甚至在你需要链接某些部分(比如线程,文件系统等)的库文件时也是如此)。 一个主要的例子是,你不能在只有头部的库中拥有简单的全局对象(除非你诉诸单身的憎恶),因为你会遇到多个定义错误。 注: C ++ 17的内联variables将使这个特定的例子在将来可行。
作为最后一点,当使用boost作为仅头部代码的例子时,往往会忽略一个巨大的细节。
Boost是库,而不是用户级代码。 所以它不会经常改变。 在用户代码中,如果你把所有东西都放在标题中,每一个小的改动都会导致你不得不重新编译整个项目。 这是一个巨大的浪费时间(而不是从编译到编译的库不变)。 当你在标题/源代码之间进行分割的时候,使用前向声明来减less包含,你可以节省几个小时的重新编译时间。
当天,C ++编程人员对The Way表示赞同,羊羔会与狮子躺在一起,巴勒斯坦人将拥抱以色列人,猫和狗将被允许结婚。
.h和.cpp文件之间的分隔大多是任意的,这是编译器优化的遗迹。 在我看来,声明属于标题,定义属于实现文件。 但是,那只是习惯,而不是宗教。
在头文件中的代码通常是一个坏主意,因为当你改变实际的代码而不是声明时,它会强制重新编译包含头文件的所有文件。 这也会减慢编译速度,因为你需要parsing每个包含头文件的代码。
在头文件中使用代码的原因是,关键字inline通常需要正确使用以及使用在其他cpp文件中实例化的模板。
什么可能是告诉你的同事是一个概念,大多数C + +代码应该模板化,以最大限度地提高可用性。 如果是模板化的,那么一切都需要放在一个头文件中,以便客户端代码可以看到并实例化它。 如果Boost和STL足够好,那对我们来说已经足够了。
我不同意这个观点,但这可能是来自哪里。
通常我会把一些简单的成员函数放到头文件中,让它们被内联。 但要把整个代码放在那里,只是为了与模板一致? 那简直是疯了
请记住: 愚蠢的一致性是小心灵的大地精 。
我认为你的同事聪明,你也是对的。
我发现将所有内容放入标题的有用之处在于:
-
无需编写和同步标题和来源。
-
结构很简单,没有任何循环依赖会迫使编码器做出“更好”的结构。
-
便携,易于embedded到一个新的项目。
我同意编译时间问题,但我认为我们应该注意到:
-
源文件的更改很可能会改变导致整个项目重新编译的头文件。
-
编译速度比以前快得多。 如果你有一个长时间,高频率的项目,可能表明你的项目devise有缺陷。 将任务分成不同的项目,模块可以避免这个问题。
最后我只是想以我个人的观点来支持你的同事。
正如Tuomas所说,你的标题应该是最小的。 要完成,我会扩大一点。
我个人在我的C++
项目中使用4种types的文件:
- 上市:
- 转发标题:在模板等情况下,这个文件获得将出现在标题中的转发声明。
- 头文件:这个文件包含转发头,如果有的话,并声明我希望公开的所有东西(并定义类…)
- 私人的:
- 私有头文件:这个文件是为实现保留的头文件,它包含头文件并声明了辅助函数/结构(例如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; 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
这里的救星是大多数时候,前端头是无用的:只有在typedef
或template
情况下,实现头是必要的;)
如果这种新的方式真的是方式 ,我们可能会在我们的项目中走向不同的方向。
因为我们试图避免标题中的所有不必要的东西。 这包括避免标题级联。 代码在标题将propably需要一些其他头被包括,这将需要另一个头等。 如果我们被迫使用模板,我们尽量避免乱七八糟的标题与模板的东西太多。
我们也使用“不透明指针”模式 。
通过这些实践,我们可以比我们大多数同行做更快的构build。 是的…改变代码或类成员不会造成巨大的重build。
要添加更多乐趣,可以添加包含模板实现(包含在.hpp
)的.ipp
文件,而.hpp
包含该接口。
除了模板化的代码(取决于项目,这可以是多数或less数文件)有正常的代码 ,这里最好是分开声明和定义。 在需要的地方也提供前向声明 – 这可能会影响编译时间。
一般来说,在编写一个新的类的时候,我会把所有的代码都放在这个类中,所以我不用再去查看另外一个文件了。一切正常后,我把方法的主体分解成cpp文件,将原型留在hpp文件中。
我个人在我的头文件中做到这一点:
// class-declaration // inline-method-declarations
我不喜欢把这些方法的代码和课程混合在一起,因为我发现很快就要查找一些东西。
我不会把所有的方法放在头文件中。 编译器将(通常)不能内联虚拟方法,并且(可能)只能内联没有循环的小方法(完全取决于编译器)。
在课堂上做的方法是有效的…但从可读性的angular度来看,我不喜欢它。 将这些方法放在标题中意味着,如果可能的话,它们将被内联。
恕我直言,他有优点,只要他做模板和/或元编程。 有很多原因已经提到您将头文件限制为声明。 他们只是…头。 如果你想包含代码,你可以把它编译成一个库并把它连接起来。
这不是真的取决于系统的复杂性和内部公约吗?
目前我正在研究一个非常复杂的neural network模拟器,我期望使用的公认的风格是:
classname.h中的类定义
classnameCode.h中的类代码
classname.cpp中的可执行代码
这将开发人员构build的基类从用户构build的模拟中分离出来,在这种情况下效果最好。
但是,我很惊讶地发现,人们在graphics应用程序或者其他任何其他应用程序中这样做的目的不是为用户提供代码库。
我把所有的实现放在类定义之外。 我想要从类定义中获得doxygen注释。
模板代码应该只在标题中。 除了inline之外,所有的定义都应该在.cpp中。 最好的参数是遵循相同规则的标准库实现。 你不会不同意标准库的开发人员对此是正确的。
我认为你的同事是正确的,只要他没有进入在头上写可执行代码的过程。 我认为正确的平衡是遵循GNAT Ada指出的path,在这个path下,.ads文件为用户和孩子提供了一个完全适合的软件包接口定义。
顺便说一句,你有没有看过这个论坛上的Ada绑定到你几年前写的CLIPS库最近的问题,哪些是没有更多的可用(相关网页现在已closures)。 即使制作了旧的剪辑版本,对于愿意在Ada 2012程序中使用CLIPS推理引擎的人来说,此绑定也是一个很好的开端。