Skip to content

13 PIMPL

JackHeeley edited this page Apr 2, 2020 · 22 revisions

Use PIMPL pattern/paradigm to encapsulate object implementation

Details of where, how and points of special interest are supplied here.

The App3Dev project applies this guidance in the following places:

  • file_logger
  • SystemError
  • CdromDevice
  • Device
  • DeviceDiscoverer
  • MemoryMappedFile

If you inspect the .hpp files for these objects you will find

private:

///<summary> forward reference to private implementation.</summary>

class impl;

///<summary> smart unique pointer to private implementation.</summary>

///<remarks> with default copy, move and compare support.</remarks>

spimpl::unique_impl_ptr<impl> pimpl;

This is encapsulation via a private unique pointer to an internal class. Refer to the spimpl author's own blog for a definitive description. This should make the code and concept more readily accessible. Thereafter, the half dozen examples I provide here show both rule-of-five and rule-of-zero classes being given the spimpl treatment.

Notes

  1. Some C++ Core Guidelines warnings are reported against the spimpl.h header (in some situations). It appears to me that std::forward is unable to track noexcept qualifiers, and that this unavoidably leads to false (or true) positives. I've given this point some attention, but can't claim to have found any rigorously correct treatment. Offending warnings have been disabled in the interest of reporting clean builds with my own code base. spimpl.hpp is a wrapper for spimpl.h that preserves the original header unedited. All such instances of disabling warnings should always be peer reviewed and assessed with a consensus view.

  2. Notice that rule-of-five and rule-of-zero apply to the encapsulated internal impl class, but when rule-of-five applies it doesn't spill over into more boiler plate code being needed in the exposed bridging class. This might not be what you initially expect. Don't be tempted to provide unnecessary code.

  3. Notice that inheritance relationships are interesting! Inheritance relationships are (almost) not expressed at all between exposed bridging pimpl classes, as these are usually individually self-contained. After all PIMPL is intended to encapsulate and hide implementation details and this includes any inheritance relationship. However an impl class could feasibly need to inherit from a base class. It does this using the exposed bridging pimpl class of its base/parent. This again is reasonable after a moments thought. How else could it be done when the base impl is also fully enclosed encapsulated? Look at derived class cd_rom_device and its base class device for an example of this.

  4. Notice that device_type_directory is a SINGLETON, not a full PIMPL, but it adopts the same design concept of bridging to an inner impl class, for the same reasons, namely to encapsulate platform dependent logic.

Special case

A noteworthy special case here is file_logger which, although it is pimpl, also implements a virtual base logger_interface (a header only class providing an INTERFACE design pattern). As a consequence of the design pattern, the file_logger inheritance relationship with logger_interface is also exposed to view in the file_logger header.

The advantage here is that its easy to unplug the file_logger and plug-in (say) a null_logger, or perhaps build and switch over to using a (e.g. windows) system_logger. None of that would impact the logger client code which won't need recompilation. This is particularly important when DLL's are logging. This is all possible because all client code exclusively uses generic logging features offered by the logger macros provided in logger.hpp. These macros exclusively use a shared pointer to a generic logger_interface (and never directly invoke a specific derived logger class method directly).

Clone this wiki locally