mardi 16 février 2021

Pimpl without the overhead by casting pointers?

When using the PIMPL idiom, we suffer a double pointer redirection if the pimpled object was dynamically allocated, e.g.,

auto obj = std::make_unique<PimplClass>();
obj->foo();  // Requires dereferencing {obj} and {obj.pimpl}

It seems like if we enforce clients of our class to construct instances via a std::unique_ptr, then we can have the PIMPL idiom "for free" (i.e., without an additional dereference) by recasting the instance pointer to some implementation-defined type at appropriate points:

// Header
struct SelfPimplClass
{ // No member variables/implementation details
  void foo();
  static std::unique_ptr<SelfPimplClass> create();
};

// Implementation
struct SelfPimpClassImpl
{
  int foo,bar,etc;
  void fooImpl() { ... }; 
};

static std::unique_ptr<SelfPimplClass> SelfPimplClass::create()
{
  auto deleter = [] (SelfPimplClass *ptr) 
    { delete static_cast<SelfPimplClassImpl *>(ptr); };
  auto *ptr = new SelfPimplClassImpl();
  return std::make_unique<SelfPimplClass>(static_cast<SelfPimplClass *>(ptr), deleter);
}

void SelfPimplClass::foo()
{
  return static_cast<SelfPimplClassImpl *>(this)->fooImpl();
}

There is no advantage over PIMPL in applications where clients would otherwise be using stack-allocated variables, but in cases where clients would have been using dynamic allocation anyway (e.g., because they needed a std::shared_ptr), or in cases where the PIMPL instance is being accessed from a pointer, then the code above would seem to offer significant performance advantages (half the number of memory dereferences), with the primary disadvantage being additional boilerplate code that might be abstracted away by suitable helper templating.

Am I missing anything, and if not, does this practice have a name and is it used in the wild?

Aucun commentaire:

Enregistrer un commentaire