当前位置:  开发笔记 > 编程语言 > 正文

将所有代码放在C++中的Header文件中的优缺点?

如何解决《将所有代码放在C++中的Header文件中的优缺点?》经验,为你挑选了5个好方法。

您可以构建一个C++程序,以便(几乎)所有代码都驻留在Header文件中.它本质上看起来像一个C#或Java程序.但是,.cpp在编译时,至少需要一个文件来提取所有头文件.现在我知道有些人绝对会厌恶这个想法.但我没有发现任何令人信服的缺点.我可以列举一些优点:

[1]编译时间更快.所有头文件只被解析一次,因为只有一个.cpp文件.此外,一个头文件不能包含多次,否则您将获得构建中断.在使用备用方法时,还有其他方法可以实现更快的编译,但这很简单.

[2]通过使它们绝对清楚,它避免了循环依赖.如果ClassAClassA.h对循环依赖ClassBClassB.h,我必须把前向参考与它伸出.(请注意,这与C#和Java不同,编译器会自动解析循环依赖关系.这会鼓励编写错误的编码实践IMO).同样,如果您的代码在.cpp文件中,您可以避免循环依赖,但在实际项目中,.cpp文件往往包含随机标题,直到您无法确定谁依赖于谁.

你的意见?



1> paercebal..:

原因[1]编译时间更快

不在我的项目中:源文件(CPP)仅包含他们需要的标题(HPP).因此,当我因为微小的改变而需要重新编译一个CPP时,我有十倍于未重新编译的文件.

也许您应该在更多逻辑源/头中分解您的项目:A类实现中的修改不应该需要重新编译B,C,D,E等类的实现.

原因[2]它避免了循环依赖

代码中的循环依赖?

对不起,但我还没有将这类问题作为一个真正的问题:让我们说A取决于B,B取决于A:

struct A
{
   B * b ;
   void doSomethingWithB() ;
} ;

struct B
{
   A * a ;
   void doSomethingWithA() ;
} ;

void A::doSomethingWithB() { /* etc. */ }
void B::doSomethingWithA() { /* etc. */ }

解决问题的一个好方法是将这个源分解为每个类至少一个源/头(以类似于Java的方式,但每个类有一个源和一个头):

// A.hpp

struct B ;

struct A
{
   B * b ;
   void doSomethingWithB() ;
} ;

.

// B.hpp

struct A ;

struct B
{
   A * a ;
   void doSomethingWithA() ;
} ;

.

// A.cpp
#include "A.hpp"
#include "B.hpp"

void A::doSomethingWithB() { /* etc. */ }

.

// B.cpp
#include "B.hpp"
#include "A.hpp"

void B::doSomethingWithA() { /* etc. */ }

因此,没有依赖性问题,而且编译时间仍然很快.

我错过了什么?

在处理"真实世界"项目时

在现实世界的项目中,cpp文件往往包含随机标题,直到你无法弄清楚谁依赖于谁

当然.但是,如果你有时间重新组织这些文件来构建"一个CPP"解决方案,那么你就有时间清理那些标题.我的标题规则是:

分解标题使其尽可能模块化

永远不要包含您不需要的标题

如果您需要符号,请向前声明

只有上述失败,包括标题

无论如何,所有标题必须是自给自足的,这意味着:

标头包含所有需要的标头(仅需要标头 - 见上文)

包含一个标题的空CPP文件必须编译而无需包含任何其他内容

这将删除排序问题和循环依赖.

编译时间是个问题吗?然后...

如果编译时间确实是一个问题,我会考虑:

使用预编译头文件(这对STL和BOOST非常有用)

通过PImpl惯用法减少耦合,如http://en.wikipedia.org/wiki/Opaque_pointer中所述

使用网络共享编译

结论

你正在做的不是把所有东西放在标题中.

您基本上将所有文件都包含在一个且只有一个最终来源中.

也许你在全项目编译方面取胜.

但是当编译一个小的变化时,你总是会失败.

在编码时,我知道我经常编译很小的更改(如果只是让编译器验证我的代码),然后最后一次,做一个完整的项目更改.

如果我的项目按你的方式组织,我会失去很多时间.



2> Milan Babušk..:

我不同意第1点.

是的,只有一个.cpp,从头开始构建的时间更快.但是,你很少从头开始构建.您进行了小的更改,每次都需要重新编译整个项目.

我更喜欢这样做:

保留.h文件中的共享声明

保留仅在.cpp文件中的一个地方使用的类的定义

所以,我的一些.cpp文件开始看起来像Java或C#代码;)

但是,在设计系统时,"保持.h"方法很好,因为你做了第2点.我通常在构建类层次结构时执行此操作,稍后当代码体系结构变得稳定时,我将代码移动到.cpp文件.


分歧不够强大.这不容争论:由于你提到的原因,第1点是错误的.

3> Vincent Robe..:

你说你的解决方案有效是对的.它甚至可能对您当前的项目和开发环境没有影响.

但...

正如其他人所说,每次更改一行代码时,将所有代码放在头文件中都会强制进行完整编译.这可能不是一个问题,但您的项目可能会变得足够大,以至于编译时间将是一个问题.

另一个问题是共享代码时.虽然您可能还没有直接关注,但重要的是尽可能多地保留代码的潜在用户隐藏的代码.通过将代码放入头文件中,任何使用代码的程序员都必须查看整个代码,而对如何使用它只感兴趣.将代码放在cpp文件中只允许将二进制组件(静态或动态库)及其接口作为头文件提供,这在某些环境中可能更简单.

如果您希望能够将当前代码转换为动态库,则会出现此问题.由于您没有与实际代码分离的正确接口声明,因此您将无法将已编译的动态库及其使用接口作为可读头文件提供.

您可能还没有遇到这些问题,这就是为什么我告诉您的解决方案在您当前的环境中可能没问题.但是,随时准备应对任何变化总是更好,并且应该解决其中一些问题.

PS:关于C#或Java,您应该记住这些语言没有按照您的说法进行操作.它们实际上是独立编译文件(如cpp文件),并为每个文件全局存储接口.然后使用这些接口(以及任何其他链接接口)链接整个项目,这就是他们能够处理循环引用的原因.因为C++每个文件只进行一次编译传递,所以它无法全局存储接口.这就是为什么你需要在头文件中明确地写它们.



4> 小智..:

您误解了该语言的用途..cpp文件实际上(或者应该是内联和模板代码除外)是系统中可执行代码的唯一模块..cpp文件被编译为目标文件,然后链接在一起..h文件仅用于在.cpp文件中实现的代码的前向声明.

这导致更快的编译时间和更小的可执行文件.它看起来也相当清晰,因为你可以通过查看它的.h声明来快速了解你的课程.

对于内联和模板代码 - 因为这两者都用于由编译器生成代码而不是链接器 - 它们必须始终可用于编译器每个.cpp文件.因此,唯一的解决方案是将其包含在.h文件中.

但是,我开发了一个解决方案,我在.h文件中有我的类声明,在.inl文件中有所有模板和内联代码,在我的.cpp文件中有非模板/内联代码的所有实现..inl文件在我的.h文件底部是#included.这使事情保持清洁和一致.


如果人们只按照语言的使用方式来做事,那么就没有模板元编程.哦,坚持下去,我不确定这是不是坏事;-)

5> Chris Jester..:

对我来说明显的缺点是你总是需要一次构建所有代码.对于.cpp文件,您可以进行单独编译,因此您只需重建真正已更改的位.

推荐阅读
郑小蒜9299_941611_G
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有