您使用什么模式来解耦 C++ 中的接口和实现?
大型 C++ 项目中的一个问题可能是构建时间。 您的依赖树中有一些类需要处理,但通常您会避免这样做,因为每次构建都需要很长时间。 您不一定要更改其公共接口,但也许您想更改其私有成员(添加缓存变量,提取私有方法,...)。 您面临的问题是,在 C++ 中,即使私有成员也是在公共头文件中声明的,因此您的构建系统需要重新编译所有内容。
在这种情况下你会怎么做?
我已经概述了我所知道的两种解决方案,但它们都有其缺点,也许还有一个我还没有想到的更好的解决方案。
One problem in large C++ projects can be build times. There is some class high up in your dependency tree which you would need to work on, but usually you avoid doing so because every build takes a very long time. You don't necessarily want to change its public interface, but maybe you want to change its private members (add a cache-variable, extract a private method, ...). The problem you are facing is that in C++, even private members are declared in the public header file, so your build system needs to recompile everything.
What do you do in this situation?
I have sketched two solutions which I know of, but they both have their downsides, and maybe there is a better one I have not yet thought of.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
pimpl 模式:
在头文件中,仅声明公共方法和指向前向声明的实现类的私有指针(pimpl 指针或委托)。
在源代码中,声明实现类,将公共类的每个公共方法转发给委托,并在公共类的每个构造函数中构造 pimpl 类的实例。
另外:
缺点:
The pimpl pattern:
In your header file, only declare the public methods and a private pointer (the pimpl-pointer or delegate) to a forward declared implementation class.
In your source, declare the implementation class, forward every public method of your public class to the delegate, and construct an instance of your pimpl class in every constructor of your public class.
Plus:
Minus:
John Lakos 的大规模 C++ 软件设计是一本出色的书,解决了所涉及的挑战构建大型 C++ 项目。 问题和解决办法都是立足于现实的,当然对上述问题进行了详细的讨论。 强烈推荐。
John Lakos' Large Scale C++ Software Design is an excellent book that addresses the challenges involved in building large C++ projects. The problems and solutions are all grounded in reality, and certainly the above problem is discussed at length. Highly recommended.
使用继承:
在标头中,将公共方法声明为纯虚拟方法和工厂。
在您的源代码中,从您的接口派生一个实现类并实现它。 在执行工厂中返回一个执行实例。
另外:
缺点:
Using inheritance:
In your header, declare the public methods as pure virtual methods and a factory.
In your source, derive an implementation class from your interface and implement it. In the implementation of the factory return an instance of the implementation.
Plus:
Minus:
您可以对类 A 使用前向声明,该声明由另一个类 B 中的指针引用。然后,您可以将类 A 的头文件包含在类 B 的实现文件中,而不是其头文件中。 这样,您对 A 类所做的更改将不会影响包含 B 类头文件的源文件。 任何想要访问 A 类成员的类都必须包含 A 类的头文件。
You can use a forward declaration for class A that is referred to by pointer in another class B. You can then include class's A header file in class B's implementation file rather than its header file. That way, changes you make to class A will not affect source files that include class B's header file. Any class that wants to access class A's members will have to include class A's header file.
重构并使用 pimpl/handle-body 习惯用法,使用纯虚拟接口来隐藏实现细节似乎是流行的答案。 在设计大型系统时,应该考虑编译时间和开发人员的生产力。 但是,如果您正在使用没有单元测试覆盖率的现有大型 C++ 系统怎么办? 重构通常是不可能的。
当我接触一些常见的头文件后,当我不希望编译器编译世界时,我通常所做的就是使用 makefile/脚本来仅编译我知道需要重新编译的文件。 例如,如果我向类添加非虚拟私有函数,则只需重新编译该类的 cpp 文件,即使其头文件已被一百个其他文件包含。 在我离开之前,我启动了一个干净的构建来重建世界。
Refactoring and use pimpl/handle-body idiom, use pure virtual interfaces to hide implementation detail seems to be the popular answer. One should consider compile time and developer productivity when designing large systems. But what if you're working on existing large C++ system with no unit test coverage? Refactoring is usually out of the question.
What I usually do when I don't want the compiler to compile the world after I touched some common header files is to have a makefile/script to compile only the files I know need recompiling. For example, if I'm adding a non-virtual private function to a class, only the class's cpp file needs to be recompiled even when its header file is included by a hundred other files. Before I leave for the day, I kick off a clean build to rebuild the world.
无。
我明白使用一个的意义,但我认为以下论点在许多情况下减轻了这一点:
速度必须要考虑两次,什么
关于牺牲清晰度
编译时间速度?
当然,最终这是一个经济决定。 如果“3”的权重在您的项目中很重要,并且由于某种原因“6”无法应用,那么请继续:使用这些模板您获得的收益将多于您损失的收益。
None.
I see the point in using one, but I think the following arguments mitigate that point in many scenarios:
speed must be considered twice, what
about compromising clarity for
compile time speed?
Of course in the end this is an economic decision. If the weight of "3" is important in your project and for some reason "6" cannot apply, then go ahead: you will win more from using these templates than you lose.