如何停止通过分层包含传播声明?
每当我制作 .h 头文件时,我都会想到一个问题:“如何停止通过分层包含传播声明?” 假设有以下文件:
>foo.h
#ifndef FOO_H
#define FOO_H
typedef int foo_t;
inline int foo() { return 1; }
class foo_c {};
#endif /* FOO_H */
bar.h
#ifndef BAR_H
#define BAR_H
#include "Foo.h"
typedef foo_t bar_t;
inline int bar() { return foo(); }
class bar_c : public foo_c {};
#endif /* BAR_H */
zoo.h
#ifndef ZOO_H
#define ZOO_H
#include "Bar.h"
typedef bar_t zoo_t;
inline int zoo() { return bar(); }
class zoo_c : public bar_c {};
#endif /* ZOO_H */
在文件 zoo.h 中,我们可以访问声明的元素 foo_c ,
foo_t
, foo()
,并且对 foo.h 的每次更改都会重新编译 zoo.h
我知道我们可以将实现移至 .cpp 文件中,但是在 .h 文件中的类定义中编写的代码又如何呢?如果程序员需要的话,我们如何强制他在 zoo.h 中显式包含 foo.h 呢?
作为 Qt 中的一个示例,当我包含并使用
时,我无法访问 QList
,其中 QQueue
由 继承>QList
并且我必须显式包含
。 (另外,我不知道它是如何完成的,以及它对编译时间的影响)
Whenever I make a .h header file, a question comes to my mind: "How to stop propagating declarations through hierarchical includes?" Assume there are these below files:
foo.h
#ifndef FOO_H
#define FOO_H
typedef int foo_t;
inline int foo() { return 1; }
class foo_c {};
#endif /* FOO_H */
bar.h
#ifndef BAR_H
#define BAR_H
#include "Foo.h"
typedef foo_t bar_t;
inline int bar() { return foo(); }
class bar_c : public foo_c {};
#endif /* BAR_H */
zoo.h
#ifndef ZOO_H
#define ZOO_H
#include "Bar.h"
typedef bar_t zoo_t;
inline int zoo() { return bar(); }
class zoo_c : public bar_c {};
#endif /* ZOO_H */
In file zoo.h, we can access declared elements foo_c
, foo_t
, foo()
, and every change to foo.h will re-compile zoo.h
I know we can move implementations to .cpp files, but how about the codes written in class definitions in .h files? How can we force the programmer to explicitly include foo.h in zoo.h if he needs it?
As an example in Qt, when I include and use <QQueue>
, I have no access to QList
where QQueue
is inherited by QList
and I have to include <QList>
explicitly. (Also, I dont know how it is done, and effect of it on compile time)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
在 C++ 和 C 中,“要停止传播声明”,您需要将它们从公共接口中删除。将它们移至实施。或者“不太公开”的界面。
编译时间是目标之一。其他是可移植性、可维护性。这也与松散耦合直接相关。
可以帮助您进行类派生的最流行的 C++ 技术是 Pimpl idiom。派生您的实现类,将相应的标头包含到实现 cpp 中,并在公共接口中转发声明实现。您的用户对基类一无所知,只知道您的实现的名称。
如果您想使用
typedef
,则无法停止传播。但为了提供更好的可移植性和可维护性,您可以使用与 Boost 库有效使用相同的方法:实现定义的类型(例如 这个)。每个界面设计都是可扩展性、信息隐藏 和简单性(或努力)。如果您需要存档前两个,请使用更复杂的方法。您可以提供两个公共接口:一个用于使用,另一个更广泛和更低级别的用于可扩展性。
In C++ and C, "to stop propagating declarations" you need to remove them from public interface, period. Move them to implementation. Or to "less public" interface.
Compilation time is one of goals. Others are portability, maintenability. Also this is directly related with loose coupling.
The most popular C++ technique that can help with your class derivation is Pimpl idiom. Derive your implementation class, include corresponding header into implementation cpp and forward-declare implementation in your public interface. Your users will know nothing about base class and will know only the name of your implementation.
It's not possible to stop propagation if you'd like to use
typedef
's. But to provide better portability and maintenability you can use the same approach as Boost libraries use effectively: implementation-defined type (e.g. this one).Each interface design is a tradeoff between extensibility, information hiding and simplicity (or effort). If you need to archive first two use more sophisticated approach. You can provide two public interfaces: one for usage and another one, much wider and lower-level, for extensibility.
我发现在我的代码中清楚地区分前向声明与定义非常重要:尽可能多地使用前向声明。
一般来说,如果您的 X 类不需要知道 Y 类的大小,您所需要的只是 Y 的前向声明 - 您不需要包含 Y.hpp。
例如,如果 X不是 Y 的子类,并且 X 不包含任何类型 Y 的成员,那么您不需要包含 Y.hpp。向前声明 Y 类; 就足够了。有时,为了更好地解耦我的代码,我会保留对 Y 的引用或指针,而不是将 Y 嵌入到类 X 中 - 如果这是可行的,我需要做的就是前向声明类 Y;
现在,有一条关于使用模板类时无法前向声明的评论。但是这里有一个技巧 - 不要使用 typedef,而是从您想要的模板实例化子类化,例如:
现在您可以前向声明
class Bars;
,之前您无法转发声明std::vector;
因此,这些是我在所有 C++ 项目中遵循的步骤:
#include
而不是任何#include
(前向声明优于定义)通过这种方式,标头是松散耦合的,并且在修改代码时可以获得更快的编译时间。
I find it important to clearly separate forward-declarations vs definitions in my code: use forward-declarations as much as possible.
In general, if your class X does not need to know the sizeof class Y, all you need is a forward-declaration of Y - you do not need to include Y.hpp.
For example, if X does not subclass from Y and X does not contain any members of type Y, then you don't need to include Y.hpp. Forward-declaring class Y; is sufficient. Sometimes, to decouple my code better, I will hold a reference or pointer to Y rather than embed Y in class X - if this is feasible, again, all I need to do is forward-declare class Y;
Now, there is a comment about not being able to forward-declare when you use template classes. But there is a trick around this - instead of using typedef, subclass from the template instantiation that you want eg:
Now you can forward-declare
class Bars;
, where previously you could not forward declarestd::vector<Bar>;
So, these are the steps I follow in all my C++ projects:
#include <modulename/fdecl.hpp>
over any#include <modulename/foo.hpp>
(forward declarations over definitions)In this way, the headers are loosely coupled and I get faster compile times as I modify code.
我将以这种方式重写代码:
foo.h
foo.cpp
bar.h
bar.cpp
Zoo.h
Zoo.cpp
这样,您仅在标头中公开您的接口,而实现细节保留在 .cpp 文件中。
但请注意,如果您使用模板,此策略将会失败:它们必须在标头中完全声明(否则您可能会遇到链接器问题)。
I would rewrite the code in this way:
foo.h
foo.cpp
bar.h
bar.cpp
zoo.h
zoo.cpp
This way you expose in the header only your interface, and the implementation details remain in the .cpp file/.
Please be warned however that this strategy will fail if you work with templates: they have to be fully declared in the header (otherwise you are likely to get linker problems).
也许您可以使用命名空间:
foo.h
bar.h
Zoo.h
这个示例看起来很做作,但可能只是因为代码太短了。如果您的应用程序涉及更多代码,这个技巧可能会有所帮助。
更新:
同样的原理也适用于类和其他名称。例如,使用 Qt 名称:
qt_main.h
qt_list.h
qt_queue.h
Zoo.h:
免责声明:我没有使用 Qt 的经验;此示例并未显示 Qt 开发人员实际做了什么,它仅显示了他们可以做什么。
Maybe you can use namespaces:
foo.h
bar.h
zoo.h
This example looks contrived, but maybe only because the code is so short. If your application involves much more code, this trick may prove helpful.
Update:
The same principle can work with classes and other names. For example, with the Qt names:
qt_main.h
qt_list.h
qt_queue.h
zoo.h:
Disclaimer: I have no experience with Qt; this example doesn't show what Qt developers actually did, it only shows what they could do.
你不能鱼与熊掌兼得。要么尽可能多地利用内联,要么尽可能地限制可见性。对于类,您必须在使用派生和/或直接数据成员(需要相应的类定义可用)或间接数据成员(即指针或引用)之间取得平衡,这仅需要声明类。您的方法倾向于内联/直接包含,相反的极端是:
foo.h
bar.h
zoo.h
foo.c
bar.c
zoo.c
介于两者之间的一种方法是引入额外级别的源文件,您可以将其称为
.inl
,将函数实现移至此处并使其内联。通过这种方式,您可以在原始标头之后包含这些新文件,并且仅在实际需要的地方包含这些新文件,并获得有限的可见性和最大内联。但我认为这不值得付出努力。模板会使事情变得更加复杂,因为一般来说,只要需要实例化模板,定义就必须可用。有一些方法可以控制这一点,例如通过强制实例化所需的专业化,以避免包含每个使用点的定义,但同样增加的复杂性可能不值得。
如果您担心编译时间,通常依赖编译器的标头预编译机制会更容易。
You can't have the cake and eat it too. Either you exploit inlining as much as you can or you limit visibility as much as you can. With classes you have to strike a balance between using derivation and/or direct data members, which require the corresponding class definition to be available, or indirect data members, i.e. pointers or references, which only require the class to be declared. Your approach favours inlining/direct inclusion, the opposite extreme would be:
foo.h
bar.h
zoo.h
foo.c
bar.c
zoo.c
A sort of way in between would be to introduce an additional level of source files, which you might call
.inl
, move function implementations there and make them inline. In this way you could include these new files after the original headers and only where actually needed and get both limited visibility and maximum inlining. I don't think it is worth the effort, though.Templates would complicate things even further because in general definitions must be available wherever a template needs to be instantiated. There are ways to control this, e.g. by forcing instantiations of the required specializations so as to avoid including definitions for every point of use, but again the added complication may not be worthwhile.
If you are worried about compilaton time usually it's much easier to rely on your compiler's header pre-compilation mechanism.