Search code examples
c++c++11shared-ptrpimpl-idiomunique-ptr

pimpl: Avoiding pointer to pointer with pimpl


In this question I asked "pimpl: shared_ptr or unique_ptr" I've been convinced that the proper usage of the pimpl idiom is to use a unique_ptr, not a shared_ptr. It should act to the user as if there is no pointer at all, whereas quite clearly the shared_ptr introduces aliasing upon copying, which definitely acts like a pointer.

So, lets say a user wants to create a shared_ptr to my pimpl object (say if they want actually want multiple aliases to it). For example:

shared_ptr<my_pimpl> p(new my_pimpl());

That would result in a shared_ptr pointing to a unique_ptr pointing to my implementation.

It would be nice if I could achieve something like the following:

my_pimpl x; // (1)
shared_ptr<my_pimpl> p(new my_pimpl()); // (2) Pointer to pointer here.
x.f(); // (3)
p->f(); // (4)

but with somehow getting rid of the pointer to pointer, whilst still maintaining the implementation hiding of pimpl.

Any ideas how to achieve this (I'm happy to change the line (2) and obviously my_pimpl, but want lines (3) and (4) to stay the same).


Solution

  • There are a number of possible approaches depending on your constraints.

    1. Create your own shared_my_pimpl class

    Create a class shared_my_pimpl which has the same interface as my_pimpl but internally uses a shared_ptr instead of a unique_ptr. Now create a class shared_ptr_my_pimpl which holds a shared_my_pimpl and has an operator-> which returns a pointer to the shared_my_pimpl, so that you get -> notation instead of . notation for member access. You can add a function make_shared_ptr_my_pimpl to make it look more like shared_ptr usage.

    Disadvantages:

    1. The type of the object is not shared_ptr<x> but shared_ptr_my_pimpl; it's just pretending to be a shared_ptr.
    2. You can't get a my_pimpl* or my_pimpl& to the object; it's a different type which just behaves the same.

    2. Derive from an interface

    Create an interface my_pimpl_interface with all relevant functions pure virtual. Derive both my_pimpl and my_pimpl::impl (your pimpl implementation class) from this interface. Add a function make_shared_my_pimpl which returns a shared_ptr<my_pimpl_interface> to a my_pimpl::impl. You can now refer to both the plain object and the shared_ptr object as my_pimpl_interface&.

    Disadvantages:

    1. By making all functions virtual you incur an extra indirection in calling them, which may have been what you were trying to avoid. Your standard my_pimpl object will also pay this overhead.