3

我正在开发一个相当大的 C++ 支持库,并且发现自己正在转向仅使用标头的方法。在 C++ 中,这几乎可以工作,因为您可以实现在类中定义的位置。对于模板化方法,无论如何,实现都必须在同一个文件中,所以我发现将实现与定义保持在一起要容易得多。

但是,有时必须使用“来源”。仅举一个例子,有时会出现循环依赖,并且必须在类定义之外编写实现。以下是我的处理方式:

//part of libfoo.h
class Bar
{
  void CircularDependency(void);
};

#ifdef LIBFOO_COMPILE_INLINE
void Bar::CircularDependency(void)
{
  //...
}
#endif

然后使用 libfoo 的项目将在 main.cpp 中执行以下操作:

//main.cpp
#define LIBFOO_COMPILE_INLINE
#include "libfoo.h"

在任何其他 .cpp 中:

//other.cpp
#include "libfoo.h"

关键是 compile-inline 部分只编译一次(在 main.cpp 中)。

最后我的问题是:这个成语或任何其他以这种方式工作的项目是否有名称?这似乎是模板和类方法模糊了实现和定义的自然结果。并且:是否有任何理由说明这是一个坏主意,或者为什么它可能无法很好地扩展?

顺便说一句:我知道许多编码人员有充分的理由更喜欢他们的标题类似于接口而不是实现,但是恕我直言文档生成器更适合描述接口,因为我喜欢将私有成员隐藏在一起:-)

4

3 回答 3

4

如果在 的定义出现在标题inline中时解决了循环依赖问题,您应该能够使用关键字:Bar::CircularDependency()libfoo.h

//part of libfoo.h
class Bar
{
  void CircularDependency(void);
};


// other stuff that 'resolves' the circular dependency
// ...
// ...

inline void Bar::CircularDependency(void)
{
  //...
}

这将使您的库更易于使用(用户无需处理LIBFOO_COMPILE_INLINE需要在包含标头的确切位置定义的事实)。

于 2012-03-23T18:32:52.377 回答
2

循环依赖并不是真正的问题,因为您可以转发 decalre 函数是内联的。不过,我真的不建议这样做:虽然“无源”方法最初可以更容易地使用来自某个地方的库,但它会导致更长的编译时间和文件之间更紧密的耦合。在庞大的源代码库中,这实际上是在扼杀希望在合理时间内构建代码的希望。当然巨大只从几百万行代码开始,但谁在乎琐碎的程序......?(是的,我工作的地方有数千万行代码被构建到单个可执行文件中)

于 2012-03-23T18:44:26.583 回答
2

我为什么这是一个坏主意的原因。

编译时间增加

包含头文件的每个单独的编译单元都应该编译所有头文件以及源文件本身。这可能会增加编译时间,并且在测试代码时可能会令人沮丧,只需进行少量更改。有人可能会争辩说编译器可能会对其进行优化,但 IMO 无法对其进行优化。

更大的代码段

如果所有函数都是内联编写的,则意味着编译器必须将所有代码放在调用函数的任何位置。这将炸毁代码段,并且会影响程序的加载时间,并且程序将占用更多内存。

通过紧密耦合在客户端代码中创建依赖项

每当您更改实现时,每个客户端都应该更新(通过重新编译代码)。但是如果实现已经放在一个独立的共享对象(.so 或 .dll)中,客户端应该只链接到新的共享对象。

我也不确定为什么要这样做。

//main.cpp
#define LIBFOO_COMPILE_INLINE
#include "libfoo.h"

如果必须这样做,(s)他可以简单地将实现代码放在 main.cpp 本身中。无论如何,您只能在一个编译单元中定义 LIBFOO_COMPILE_INLINE。否则,您将重复定义。

我实际上对开发一个习惯用法来编写有凝聚力的模板代码很感兴趣。将来某个时候,C++ 编译器应该支持编写内聚模板。我的意思是,只要修改了模板实现,客户端就不必重新编译代码。

于 2012-03-23T18:50:02.937 回答