据我所知,pimpl模式的存在仅仅是因为C++强制要求你将所有私有类成员放在头文件中。如果头文件只包含公共接口,理论上,类实现的任何更改都不会导致整个程序需要重新编译。
我想知道的是,为什么C++没有设计允许这样的便利。为什么它要求类的私有部分完全显示在头文件中(无意冒犯)?
据我所知,pimpl模式的存在仅仅是因为C++强制要求你将所有私有类成员放在头文件中。如果头文件只包含公共接口,理论上,类实现的任何更改都不会导致整个程序需要重新编译。
我想知道的是,为什么C++没有设计允许这样的便利。为什么它要求类的私有部分完全显示在头文件中(无意冒犯)?
这与对象的大小有关。h文件被用来确定对象的大小等信息,如果其中没有给出私有成员变量,那么您就不知道需要创建多大的对象。
不过,您可以通过以下方式模拟您所需的行为:
class MyClass
{
public:
// public stuff
private:
#include "MyClassPrivate.h"
};
这并不是强制性的行为,但它将私有内容从.h文件中分离出来。 缺点是需要维护另一个文件。 此外,在Visual Studio中,智能感知(IntelliSense)对私有成员无效 - 这可能是优点也可能是缺点。
我认为这里存在一些混淆。问题不在于头文件,头文件并没有起到什么作用(它们只是在多个源代码文件中包含公共部分的方式)。
问题,至少有一个问题,是在C ++中类声明必须定义所有实例所需的内容,包括公共和私有。(Java也是如此,但外部编译类引用的工作方式使得使用共享头文件之类的东西是不必要的。)
在常见的面向对象技术(不仅限于C ++)中,即使您仅使用公共部分,某人也需要知道所使用的具体类以及如何使用其构造函数来提供实现。第3个中的设备将其隐藏起来。第1个中的做法分离了关注点,无论您是否执行第3个步骤。
使用仅定义公共部分(主要是方法)的抽象类,并让实现类从该抽象类继承。因此,按照通常的头文件约定,共享的是抽象.hpp。还有一个声明继承类的implementation.hpp,仅传递给实现方法的模块。implementation.hpp文件将#include "abstract.hpp"用于其进行类声明的使用,以便对抽象化接口的声明有单一的维护点。
现在,如果要强制隐藏实现类声明,则需要某种方式来请求构造具体实例,而不需要拥有特定的完整类声明:无法使用new,也无法使用本地实例。引入帮助函数(包括在其他类上提供对类实例的引用的方法)是替代方法。
在用作抽象类/接口共享定义的头文件中,除了包含函数签名外,还应包含外部辅助函数的函数签名。这些函数应在特定类实现的模块中实现(因此它们可以看到完整的类声明并调用构造函数)。辅助函数的签名可能与构造函数非常相似,但它返回一个实例引用作为结果(该构造函数代理可以返回NULL指针,甚至可以抛出异常,如果您喜欢这种方式的话)。辅助函数构造特定的实现实例,并将其转换为抽象类实例的引用。
任务已完成。
啊,当只有实现发生变化时,重新编译和重新链接应按您想要的方式工作,避免调用模块的重新编译(因为调用模块不再对实现进行任何存储分配)。
你们都忽略了问题的要点:
为什么开发人员必须手动输入PIMPL代码?
对我来说,我能想到最好的答案就是我们没有一个很好的方式来表达C ++代码,使您可以对其进行操作。例如,编译时(或预处理器等)反射或代码DOM。
C++非常需要其中一个或两者对开发人员可用以进行元编程。
那么,您可以在公共MyClass.h中编写以下内容:
#pragma pimpl(MyClass_private.hpp)
可能是因为在将类的实例按值传递、聚合到其他类中等情况下需要知道其大小。
如果C++不支持值语义,那就没事了,但它确实支持。
是的,但是...
你需要阅读Stroustrup的《C++设计与演化》一书。它可能会抑制对C++的使用。