devxlogo

Use the Pimpl Idiom to Reduce Compilation Time and Enhance Encapsulation

Use the Pimpl Idiom to Reduce Compilation Time and Enhance Encapsulation

ong compilation time is an issue of concern for many large-scale C++ projects. In some cases, a typical build cycle takes up a whole night or an entire weekend. This problem is aggravated by the fact that changes in the implementation details of a single infrastructure class trigger a wholesale recompilation of numerous classes that refer to the modified class. This solution will show you how to apply the Pimpl idiom to reduce compile-time dependencies, and thus reduce compilation time. Another benefit of using this technique is a higher level of encapsulation.


How can you avoid a wholesale recompilation when a change in the implementation details of a frequently-used class has taken place?


Use the Pimpl idiom, also known as the compiler-firewall idiom.

Presenting the Problem
Suppose you are designing a custom string class that is used in a large-scale project. This means that dozens of other classes and modules will depend on the header file in which the string class is defined. Here’s a slightly contrived implementation of this class for demonstration purposes:

//+++ file String.h#include #include #include "Lock.h"class String{public: String(); ~String();//..copy ctor et al. std::size_t length() const; std::ostream & operator  vc; std:size_t len; Lock lck; //multithreading support};

Each client that uses this class has to #include the header String.h. The problem is that whenever you change the private section of this class, every piece of code that uses this class has to be recompiled. Not only does this dependency increase compilation time considerably, it also compromises encapsulation.

Remove Redundant #includes
String.h #includes three other headers. The first two are known to take relatively long to compile because they are made up of several other internal headers and template code. Notice, however, that String.h contains only declarations of its member functions. Therefore, you don’t really need the bulky header to compile it. Replace it with the lightweight header . Since String.h is #included in multiple source files, the overall effect can be noticeable.

Is it possible to remove and Lock.h as well? Yes, it is.

Apply the Pimpl Idiom
Originally introduced by James Coplien in 1992, the Pimpl idiom has undergone several revisions and name changes. The name Pimpl itself stands for “impl” with the prefix “p”, following the Hungarian naming convention. Pimpl is a method of hiding implementation details by moving private members (both data and functions) of a class into an internal private struct.

In String.h, you provide only forward declaration of this internal struct (called StringImpl in this example) and add an opaque pointer to it. The modified String.h file now looks like this:

 //+++ file String.h after applying the Pimpl idiom#include class String{public: String (); ~String(); //... std::size_t length() const; std::ostream & operator 

In the corresponding String.cpp file (which, unlike String.h, is compiled only once), you add the definition of struct StringImpl along with the rest of String's member functions:

//+++file String.cpp#include #include "Lock.h"#include "String.h"struct String::StringImpl{ std::vector vc; std::size_t len; Lock lck;};String::String(): pimpl (new String::StringImpl) {}String::~String() {delete pimpl; }//..additional member functions

Every String object allocates its own StringImpl object dynamically during construction. String's member functions access the StringImpl members using the pointer pimpl:

//+++file String.cppstd::size_t String::length() const{ return pimpl->len;}

Now you can modify StringImpl (say by replacing the vector data member with char *) without recompiling the entire project.

A Pimpl in Time
In essence, the Pimpl idiom moves code from a header file that gets compiled several times (once for each translation unit that #includes it), into a single source file which gets compiled only once. The two major advantages of this are:

  • You can freely change the implementation details of String without having to recompile code that uses that class.
  • Datatypes that are used only as private members (in this example: std::vector, Lock and std::size_t) need no longer be defined in String.h. This means that you can remove extra #includes from String.h, thus reducing compilation time.

If the original class contained many private members, and its header file is #included in numerous, separately-compiled files, applying this idiom can reduce compilation time significantly. That said, you already know that there's no such thing as a free lunch. While a Pimpled class may reduce compilation time and enhance encapsulation, it exacts performance penalties:

  • Each construction and destruction allocates and release memory dynamically.
  • String accesses its hidden data members using a pointer, whereas the non-Pimpled version accesses them directly.

Therefore, it's always best to experiment with a few key-classes in a project and learn how this affects your project's build-time.

devx-admin

Share the Post: