The User Perspective
Let's say we need to write a Pimpl-based Book
class with pointer semantics. That is:
a) We want to separate interface and implementation and to hide implementation through the deployment of the Pimpl idiom.
b) Our Book
class needs to have pointer semantics, i.e. with regard to the underlying data it will behave in the smart-pointer/proxy fashion by allowing shared access to the underlying implementation data.
In that setting the Book
class public declaration is quite likely to look like the following:
class Book { public: Book(string const& title, string const& author); string const& title() const; string const& author() const; bool operator==(Book const& that) const { return impl_ == that.impl_; } bool operator!=(Book const& that) const { return !operator==(that); } operator bool() const { return impl_; } private: struct Implementation; boost::shared_ptr<Implementation> impl_; };
Thanks to boost::shared_ptr
, applying the Pimpl idiom is fairly straightforward as boost::shared_ptr
takes care of much of the scaffolding hassle. As a result, the auto-generated destructor, copy constructor and assignment operator suffice and writing the comparison operators is child's play. What more could we wish for? For one thing, lumping the application interface with the infrastructure scaffolding is messy. Moreover, in our (admittedly simple) Book
class more than half of the code is the Pimpl-related scaffolding. For one class in isolation that might not look like that big a deal. On a larger scale, analyzing and maintaining twice as much code, mentally separating the application interface from the infrastructure scaffolding, and making sure nothing is forgotten, misused, or misplaced is a tiring exercise and not exactly fun. The following, therefore, seems like a worthwhile improvement:
struct Book : public pimpl<Book>::pointer_semantics { Book(string const& title, string const& author); string const& title() const; string const& author() const; };
It is considerably shorter, application-focused and reasonably self-explanatory. It is lean and mean, consisting of nothing but pure application-specific public interface. It does not even need to be a class as there is nothing to restrict access to.
Probably due to the specificity of my task (and/or my programming style) I have been using Pimpl-based classes with pointer semantics (as in the example above) almost exclusively. However, it is certainly not always the right solution. Let's say we needed a Pimpl-based Book
class but with value semantics instead. That is, we still want to have interface and implementation properly separated to hide implementation, but our Book
class needs to be a class with every Book
instance having and managing its own internal data (rather than sharing).
If boost::shared_ptr
is an indisputable favorite for Pimpls with pointer semantics, its deployment for Pimpls with value semantics is certain to cause a debate about efficiency, associated overhead, etc. However, writing and getting right a "raw" Pimpl-based class is certainly more involved and possibly more challenging with all things considered. The corresponding declaration might look as follows:
class Book { public: Book(string const& title, string const& author); string const& title() const; string const& author() const; bool operator==(Book const&) const; bool operator!=(Book const&) const; Book(Book const&); Book& operator=(Book const&); ~Book(); private: struct Implementation; Implementation* impl_; };
Again, the interface can be improved upon and shrunk to:
struct Book : public pimpl<Book>::value_semantics { Book(string const& title, string const& author); string const& title() const; string const& author() const; bool operator==(Book const&) const; };
It is still almost three times shorter and it consists of pure application-related public interface. Clean, minimal, elegant.
Both presented pimpl-based declarations (with pointer and value semantics) look almost identical and internal implementations (as we'll see later) are as close. A notable difference is that for value-semantics classes the comparison operators are not freebies as with pointer-semantics classes. Well, the comparison operators are never freebies (they are never auto-generated). However, due to the specificity of classes with pointer semantics those comparison operators can be reduced to pointer comparisons and generalized. Clearly, that's not true with value-semantics classes. If such a class needs to be comparable, we have to write those comparison operators ourselves. That is, for value-semantics classes the comparison operators become part of the user-provided interface. Still, pimpl<>
tries to help and adds
bool operator!=(T const& that) const
when
bool operator==(T const& that) const
is supplied.
So far our public for-all-to-see interface looks clean, minimal, and application-related. Our Book
class users and those who are to maintain the code later are likely to appreciate that. Let’s have a look at the implementation.