据我所知,pimpl习语只是因为C++强制你将所有私有类成员放在标题中而存在.如果标题只包含公共接口,理论上,类实现的任何更改都不需要重新编译程序的其余部分.
我想知道的是为什么C++不是为了方便而设计的.为什么它要求一个类的私有部分公开显示在标题中(没有双关语)?
我觉得这里有一个混乱.问题不在于标题.标头不做任何事情(它们只是在几个源代码文件中包含公共位源文本的方法).
问题,就像有一个问题一样,C++中的类声明必须定义实例为了工作而需要具有的所有公共和私有.(Java也是如此,但是外部编译类的引用方式使得不必使用共享头之类的东西.)
有些人需要知道所使用的具体类以及如何使用其构造函数来提供实现,即使您只使用公共部分,这也是常见的面向对象技术(不仅仅是C++)的本质.(3,下面)中的设备隐藏它.无论你是否做(3),(1,下面)中的做法将关注点分开.
使用仅定义公共部分的抽象类,主要是方法,并让实现类继承该抽象类.因此,使用标头的通常约定,有一个共享的abstract.hpp.还有一个implementation.hpp声明了继承的类,它只传递给实现实现方法的模块.implementation.hpp文件将#include"abstract.hpp"用于它所做的类声明,因此对于抽象接口的声明只有一个维护点.
现在,如果你想强制隐藏实现类声明,你需要有一些方法来请求构造一个具体的实例而不拥有特定的,完整的类声明:你不能使用new,你不能使用本地实例.(您可以删除.)辅助函数的引入(包括提供对类实例的引用的其他类的方法)是替代.
与用作抽象类/接口的共享定义的头文件一起或作为其一部分,包括外部帮助器函数的函数签名.这些函数应该在作为特定类实现的一部分的模块中实现(因此他们看到完整的类声明并且可以运用构造函数).辅助函数的签名可能与构造函数的签名非常相似,但它返回一个实例引用作为结果(此构造函数代理可以返回NULL指针,如果您喜欢这种东西,它甚至可以抛出异常).辅助函数构造一个特定的实现实例,并返回它作为对抽象类实例的引用.
任务完成.
哦,重新编译和重新链接应该按照您想要的方式工作,避免在仅实现更改时重新编译调用模块(因为调用模块不再为实现进行任何存储分配).
这与对象的大小有关.除其他外,h文件用于确定对象的大小.如果没有给出私有成员,那么你就不会知道一个对象有多大.
但是,您可以通过以下方式模拟您想要的行为:
class MyClass { public: // public stuff private: #include "MyClassPrivate.h" };
这不会强制执行该行为,但它会从.h文件中获取私有内容.在不利方面,这会添加另一个要维护的文件.此外,在visual studio中,intellisense对私人成员不起作用 - 这可能是加号或减号.
你们都忽视了问题的关键点 -
为什么开发人员必须输入PIMPL代码?
对我来说,我能想到的最好的答案是我们没有一个很好的方式来表达允许你对其进行操作的C++代码.例如,编译时(或预处理器,或其他)反射或代码DOM.
C++急需一个或两个这样的开发人员来进行元编程.
然后你可以在你的公共MyClass.h中写这样的东西:
#pragma pimpl(MyClass_private.hpp)
然后编写自己的,非常简单的包装器生成器.
有人会比我有更冗长的答案,但快速响应是双重的:编译器需要知道结构的所有成员以确定存储空间要求,编译器需要知道这些成员的顺序以确定的方式生成偏移.
语言已经相当复杂; 我认为在代码中划分结构化数据定义的机制将是一个灾难.
通常,我总是看到用于以Pimpl方式定义实现行为的策略类.我认为使用策略模式有一些额外的好处 - 更容易交换实现,可以轻松地将多个部分实现组合到一个单元中,这允许您将实现代码分解为功能,可重用单元等.