PrevUpHomeNext

Pimpl with Exclusive-Ownership Properties

Pimpl with the exclusive-ownership properties is a unique-proxy that owns and manages the data. For such Pimpls the definition might look as follows:

struct Book
{
  ~Book ();
   Book (string const& title, string const& author);

   string const&  title() const;
   string const& author() const;

   // If Book needs to be copyable
   Book (Book const&);
   Book& operator=(Book const&);

   // If Book needs to be moveable
   Book (Book&&);
   Book& operator=(Book&&);

   // If Book needs to be comparable
   bool operator==(Book const&) const;
   bool operator!=(Book const&) const;

   // If Book needs to support an invalid state
   explicit operator bool () const { return  bool(impl_); }
   bool         operator! () const { return !bool(impl_); }

   private:

   struct implementation;
   implementation* impl_;
};

In the example above it is only natural to reach out for std::unique_ptr ([13], [14], [15]) instead of the raw pointer. However, std::unique_ptr has been developed with certain strict goals in mind -- maximum efficiency and no (additional) overhead. So, it intentionally avoids the overhead associated with the incomplete-type management (available with std::shared_ptr). Consequently, for Pimpl purposes the benefits of the std::unique_ptr deployment are relatively modest:

struct Book
{
  ~Book ();
   Book (string const& title, string const& author);

   string const&  title() const;
   string const& author() const;

   // If Book needs to be copyable
   Book (Book const&);
   Book& operator=(Book const&);

   // If Book needs to be moveable
   Book (Book&&);
   Book& operator=(Book&&);

   // If Book needs to be comparable
   bool operator==(Book const&) const;
   bool operator!=(Book const&) const;

   // If Book needs to support an invalid state
   explicit operator bool () const { return  bool(impl_); }
   bool         operator! () const { return !bool(impl_); }

   private:

   struct implementation;
   std::unique_ptr<implementation> impl_;
};

To address that boost::impl_ptr (as std::shared_ptr) internally implements the incomplete-type-management technique (originally by Peter Dimov) that is used for the Pimpls with exclusive-ownership properties. That allows the code above to shrink down to:

struct Book : boost::impl_ptr<Book>::unique
{
   Book(string const& title, string const& author);

   string const&  title() const;
   string const& author() const;

   // If Books need to be comparable
   bool operator==(Book const&) const;
   bool operator!=(Book const&) const;
};

with the same benefits as before -- shorter, application-focused and reasonably self-documented.

As the impl_ptr<Book>::unique name suggests the Book class is moveable but not copyable similar to std::unique_ptr behavior. Changing the policy to

struct Book : boost::impl_ptr<Book>::copied { ... };

makes the Book moveable and copyable and, in fact, copied as in:

struct ubook : boost::impl_ptr<ubook>::unique { ... };
struct cbook : boost::impl_ptr<cbook>::copied { ... };

ubook b1 (args);
ubook b2 = b1; // Compile error

cbook b3 (args);
cbook b4 = b3; // b4 is a deep copy of b3
[Note] Note

One notable difference (compared to impl_ptr<Book>::shared) is that the comparison operators are now user's responsibility. In fact, they are never freebies (they are never auto-generated). However, in the case of the pointer-semantics classes those operators are reduced to pointer comparisons and generalized. That's not applicable to the value-semantics classes. Consequently, the comparison operators remain part of the user-provided interface (if such a class needs to be comparable).

So far the three impl_ptr-based deployments (using the shared, unique and copied ownership policies respectively) look almost identical and internal implementations (as we'll see later) are as close. That is important for orderly evolution of commercial large-scale systems as it allows to minimize the required effort and the impact of a design or requirement change.


PrevUpHomeNext