21

The pImpl Idiom

 6 years ago
source link: https://www.tuicool.com/articles/hit/VBRJBfi
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

The pImpl idiom is a useful idiom in C++ to reduce compile-time dependencies. Here is a quick overview of what to keep in mind when we implement and use it.

What is it?

The pImpl Idiom moves the private implementation details of a class into a separate structure. That includes private data as well as non-virtual private methods.

The key to this idiom is to only forward-declare the implementation struct in the class header and own onw instance via a pointer. With naming conventions ofprefixing pointers with p the pointer is often named pImpl , giving the idiom its name. The naming convention may differ, e.g. in Qt it’s d – sticking to a name is useful to make the idiom recognizable.

//MyClass.h
#include <memory>
class MyClass {
public:
  explicit MyClass(int i);

  //...

  int getSomething() const;
  void doSomething();

private:
  struct Impl;
  std::unique_ptr<Impl> pImpl;
};

//MyClass.cpp
#include <MyClass.h>

struct MyClass::Impl {
  int i;

  void twice() {
    i *= 2;
  }
  void half() {
    i /= 2;
  }
};

MyClass::MyClass(int i) 
  : pImpl{new Impl{i}}
{}

int MyClass::getSomething() const {
  return pImpl->i;
}

void MyClass::doSomething() {
  if (pImpl->i % 2 == 0) {
    pImpl->half();
  } else {
    pImpl->twice();
  }
}

//...

What is it used for?

The use of the pImpl idiom is twofold: it can greatly reduce compile time dependencies and stabilize the ABI of our class.

Compile time firewall

Because of the reduced dependencies, the pImpl idiom sometimes is also called a “compile time firewall”: Since we move all data members into the opaque Impl struct, we need to include the headers that declare their classes only into the source file. The classes of function parameters and return types need only beforward-declared.

This means that we need only include <memory> for the unique_ptr , headers of base classes, and the occasional header of typedefs for which forward declarations are not possible. In the end, translation units that include MyClass.h have potentially fewer headers to parse and compile.

ABI stability

Changes to private implementation details of a class usually mean that we have to recompile everything. Changes in data members mean that the layout and size of objects change, changes in methods mean that overload resolution has to be reevaluated.

With pImpl, that is not the case. The class will always only have one opaque pointer as the only member. Private changes do not affect the header of our class, so no clients have to be recompiled.

How to impl the pImpl

The example above shows a sketch of how we can implement the pImpl idiom. There are some variations and caveats, and the //... indicates that I’ve left some things out.

Rule of 5

The Impl struct is only forward-declared. That means the compiler can not generate the destructor and other member functions of the unique_ptr for us.

So, we have to declare them in the header and provide an implementation in the source file. For the destructor and move operations, defaulting them should suffice. The copy operations should either be explicitly deleted (they are implicitly deleted due to the unique_ptr ) or implemented by performing a deep copy of the impl structure.

MyClass::MyClass(MyClass&&) = default;
MyClass::MyClass(MyClass const& other)
  : pImpl{std::make_unique<Impl>(*other.pImpl)}
{}
MyClass::~MyClass() = default;
MyClass& MyClass::operator=(MyClass&&) = default;
MyClass& MyClass::operator=(MyClass const& other) {
  *pImpl = *other.pImpl;
  return *this;
}

The Impl struct

The Impl struct should besimple. Its only responsibility is to be a collection of the private details of the outer class. That means, it should not contain fancy logic in itself, only the private methods of the outer class.

It also means that it does not need its own header since it is used in one place only. Having the struct in another header would enable other classes to include it, needlessly breaking encapsulation.

Inner class or not?

The impl struct can be either an inner class of the actual class, or it can be a properly named standalone class, e.g. MyClassImpl or MyClassPrivate . I usually choose the private inner structure so that the access to its name is really restricted to the implemented class, and there are no additional names in the surrounding namespace. In the end, the choice is mostly a matter of preference – the important thing is to stick to one convention throughout the project.

What not to do

Don’t derive from the Impl struct

I’ve heard of deriving from the Impl struct as an argument to put it in its own header. The use case of deriving would be overriding parts of the implementation in a derived class of the outer class.

This will usually be a design smell since it mixes the aggregation of private details with polymorphism by making those details not so private at all. If parts of the base class behavior have to be overridden, consider using the strategy pattern or similar behavioral patterns and provide a protected method to exchange the strategy.

Don’t overuse it

The pImpl idiom comes at a cost: Allocating memory is relatively costly in terms of performance. It’s possible to use specialized allocators, but that only trades the performance cost for complexity, and it’s not scalable to a large number of classes. That’s why using the pImpl idiom everywhere just because we can is a bad idea.


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK