19

据我了解,pimpl 习惯用法的存在只是因为 C++ 强制您将所有私有类成员放在标题中。如果标头仅包含公共接口,理论上,类实现的任何更改都不需要重新编译程序的其余部分。

我想知道的是为什么 C++ 的设计不是为了提供这样的便利。为什么它要求一个类的私有部分在标题中公开显示(不是双关语)?

4

6 回答 6

10

我认为这里有一个混淆。问题不在于标题。标头不做任何事情(它们只是在几个源代码文件中包含源文本的公共位的方法)。

问题在于,C++ 中的类声明必须定义实例工作所需的所有内容,无论是公共的还是私有的。(Java 也是如此,但是对外部编译类的引用的工作方式使得不需要使用任何像共享头这样的东西。)

即使您只使用公共部分,人们也需要知道所使用的具体类以及如何使用其构造函数来交付实现,这是常见的面向对象技术(不仅仅是 C++ 技术)的本质。(3,下)中的设备将其隐藏。(1,下面)中的实践将关注点分开,无论您是否这样做(3)。

  1. 使用只定义公共部分的抽象类,主要是方法,并让实现类从该抽象类继承。因此,使用通常的标题约定,有一个abstract.hpp 是共享的。还有一个 implementation.hpp 声明了继承的类,并且只传递给实现实现方法的模块。implementation.hpp 文件将#include "abstract.hpp" 用于它所做的类声明,因此抽象接口的声明只有一个维护点。

  2. 现在,如果你想强制隐藏实现类声明,你需要有某种方法来请求构造一个具体实例而不拥有特定的、完整的类声明:你不能使用 new 也不能使用本地实例. (但您可以删除。)辅助函数的引入(包括其他类上提供对类实例的引用的方法)是替代品。

  3. 与用作抽象类/接口的共享定义的头文件一起或作为头文件的一部分,包括外部辅助函数的函数签名。这些功能应该在作为特定类实现的一部分的模块中实现(因此它们可以看到完整的类声明并可以使用构造函数)。辅助函数的签名可能很像构造函数的签名,但它返回一个实例引用作为结果(这个构造函数代理可以返回一个 NULL 指针,如果你喜欢这种东西,它甚至可以抛出异常)。辅助函数构造一个特定的实现实例并将其作为对抽象类实例的引用返回。

任务完成。

Oh, and recompilation and relinking should work the way you want, avoiding recompilation of calling modules when only the implementation changes (since the calling module no longer does any storage allocations for the implementations).

于 2008-11-06T20:36:38.773 回答
9

这与对象的大小有关。除其他外,h 文件用于确定对象的大小。如果没有在其中给出私有成员,那么您将不知道新的对象有多大。

但是,您可以通过以下方式模拟您想要的行为:

class MyClass
{
public:
   // public stuff

private:
#include "MyClassPrivate.h"
};

这不会强制执行该行为,但它会从 .h 文件中获取私有内容。不利的一面是,这增加了另一个要维护的文件。此外,在视觉工作室中,智能感知不适用于私人成员 - 这可能是一个加号或减号。

于 2008-11-06T16:07:03.420 回答
8

你们都忽略了问题的重点-

为什么开发人员必须输入 PIMPL 代码?

对我来说,我能想出的最佳答案是我们没有一种很好的方式来表达允许您对其进行操作的 C++ 代码。例如,编译时(或预处理器,或其他)反射或代码 DOM。

C++ 迫切需要其中一种或两种可供开发人员使用以进行元编程。

然后你可以在你的 public MyClass.h 中写这样的东西:

#pragma pimpl(MyClass_private.hpp)

然后编写你自己的,非常简单的包装器生成器。

于 2008-11-06T17:48:16.150 回答
6

有人会给出比我更冗长的答案,但快速响应有两个方面:编译器需要知道结构的所有成员以确定存储空间要求,编译器需要知道这些成员的顺序以以确定的方式生成偏移量。

语言已经相当复杂了。我认为在代码中拆分结构化数据定义的机制将是一场灾难。

通常,我总是看到用于以 Pimpl 方式定义实现行为的策略类。我认为使用策略模式还有一些额外的好处——更容易交换实现,可以轻松地将多个部分实现组合成一个单元,这允许您将实现代码分解为功能性、可重用单元等。

于 2008-11-06T16:02:59.497 回答
3

可能是因为在通过值传递其实例、在其他类中聚合它等时需要类的大小?

如果 C++ 不支持值语义,它会很好,但它确实支持。

于 2008-11-06T16:02:44.460 回答
2

对,但是...

你需要阅读 Stroustrup 的“Design and Evolution of C++”一书。它会抑制 C++ 的使用。

于 2008-11-06T17:33:28.567 回答