【问题标题】:What patterns do you use to decouple interfaces and implementation in C++?您使用什么模式来解耦 C++ 中的接口和实现?
【发布时间】:2010-11-01 15:51:48
【问题描述】:

大型 C++ 项目中的一个问题可能是构建时间。在你的依赖树中有一些你需要处理的类,但通常你会避免这样做,因为每次构建都需要很长时间。您不一定要更改其公共接口,但也许您想更改其私有成员(添加缓存变量,提取私有方法,...)。您面临的问题是,在 C++ 中,甚至私有成员都在公共头文件中声明,因此您的构建系统需要重新编译所有内容。

遇到这种情况你会怎么做?

我已经草拟了两个我知道的解决方案,但它们都有自己的缺点,也许还有一个更好的解决方案我还没有想到。

【问题讨论】:

    标签: c++ delegation pimpl-idiom


    【解决方案1】:

    pimpl 模式:

    在您的头文件中,仅将公共方法和私有指针(pimpl-pointer 或委托)声明到前向声明的实现类。

    在您的源代码中,声明实现类,将您的公共类的每个公共方法转发给委托,并在您的公共类的每个构造函数中构造您的 pimpl 类的实例。

    加号:

    • 允许您更改类的实现,而无需重新编译所有内容。
    • 继承效果很好,只是语法有点不同。

    减号:

    • 要编写大量愚蠢的方法体来执行委托。
    • 调试有点尴尬,因为您有大量的委托要单步执行。
    • 基类中有一个额外的指针,如果您有很多小对象,这可能是个问题。

    【讨论】:

      【解决方案2】:

      John Lakos 的Large Scale C++ Software Design 是一本出色的书,它解决了构建大型 C++ 项目所涉及的挑战。问题和解决方案都是基于现实的,当然上面的问题也有详细的讨论。强烈推荐。

      【讨论】:

      • 同意——经典之作,虽然读起来有点慢。顺便说一句,我听说他要发布第二版——有人听说过这方面的更新吗? +1
      • Drew - 你还记得你在哪里听到的(大约是第 2 版)吗?此外,我看到 Lakos 将在 2006 年出版一本关于 Scalable C++ 的书,但它似乎已经胎死腹中(或至少被搁置了。)这是一种耻辱,因为这本书 (LSCSD) 确实解决了一个需求,我希望 13 年后会发布更新。
      • Dan——我希望我能记得——我以为它在 AW 网站上,但我再也看不到它了。甚至可能是亚马逊?也许只是我的一厢情愿......
      【解决方案3】:

      使用继承:

      在您的标头中,将公共方法声明为纯虚拟方法和工厂。

      在您的源代码中,从您的接口派生一个实现类并实现它。在工厂的实现中返回一个实现的实例。

      加号:

      • 允许您更改类的实现,而无需重新编译所有内容。
      • 易于实施且万无一失。

      减号:

      • 定义公共基类的(公共)派生实例真的很尴尬,它应该继承公共基类的(私有)实现的一些方法。

      【讨论】:

      • 另一个好处是,如果你有一个接口,你可以轻松地模拟类进行单元测试
      【解决方案4】:

      您可以对由另一个类 B 中的指针引用的类 A 使用前向声明。然后您可以将类的 A 头文件包含在类 B 的实现文件中,而不是其头文件中。这样,您对 A 类所做的更改不会影响包含 B 类头文件的源文件。任何想要访问 A 类成员的类都必须包含 A 类的头文件。

      【讨论】:

      • 这是正确的,无论如何都是最佳实践。不过,有时这似乎还不够(当您有很多需要使用该类的客户时)。
      【解决方案5】:

      重构和使用 pimpl/handle-body 成语,使用纯虚拟接口隐藏实现细节似乎是流行的答案。在设计大型系统时,应该考虑编译时间和开发人员的生产力。但是,如果您正在使用没有单元测试覆盖率的现有大型 C++ 系统怎么办?重构通常是不可能的。

      当我在接触了一些常见的头文件后不想让编译器编译世界时,我通常会做一个 makefile/script 来只编译我知道需要重新编译的文件。例如,如果我正在向一个类添加一个非虚拟私有函数,那么即使它的头文件包含在一百个其他文件中,也只需要重新编译该类的 cpp 文件。在我离开这一天之前,我开始了一个干净的构建来重建世界。

      【讨论】:

      • 您的 makefile 中有通用目标还是手动编译和链接?
      【解决方案6】:

      无。

      我看到了使用一个的意义,但我认为以下论点在许多情况下都可以减轻这一点:

      1. 清晰是第一位的。如果影响运行时的清晰度 速度必须考虑两次,什么 关于妥协的清晰度 编译时间速度?
      2. 私人成员不应该经常更换。
      3. 通常,重建所有内容并不需要那么
      4. 未来会出现更快的工具,因此编译速度问题将自动缓解。您的代码不会自动变得更清晰。
      5. 无论如何,您应该经常重建。
      6. 你试过Incredibuild吗?

      当然,这最终是一个经济决定。如果“3”的权重在您的项目中很重要,并且由于某种原因“6”不能适用,那么请继续:使用这些模板您将获得比您失去的更多的收益。

      【讨论】:

      • 你的观点当然是正确的,这种事情不应该过早地做,因为你不应该在分析之前优化代码。我不同意第 2 点——软件不是静态的,而是不断发展的。如果有的话,你应该尽量保持你的公共接口不变,但是当你重构东西时,即使是这样也会很快改变。我也不同意 4。更快的工具将会出现,但与此同时,开发人员将添加大量的 loc。第 5 点是绝对正确的,也是您想要使用这种模式的部分原因。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多