C++ is infamous for long compilation times.
Part of the problem is that editing private
declarations
of a class causes recompilation for all of its users.
There are several ways to work around this problem and reduce incremental compile times:
- Use interfaces. A lot of code using a concrete class could use an interface instead. This comes with a run-time price of using virtual functions.
- The Pimpl idiom (private pointer to implementation), achieves the same compile-time benefit as interfaces without the virtual calls, but comes at a run-time price of allocating and using additional heap objects.
- The Priv idiom, introduced below, does not sacrifice runtime performance at all, but it also only brings part of the compilation time benefits of the other approaches.
Below is a short introduction to Pimpl
and
Priv
, some benchmark results, and closing commentary.
Illustrating Pimpl with a simple example
class SpamFilter
{
public:
(const std::set<std::string>& forbiddenWords);
SpamFilter ~SpamFilter();
bool isSpam (const std::vector<std::string>& words) const;
private:
class Impl;
std::unique_ptr<Impl> impl;
};
Using the Pimpl idiom, our class doesn't have any
private
declarations apart from the internal
Impl
class and impl
member.
The actual SpamFilter::Impl
class is defined in the
.cpp
file and does all of the work.
The benefit of this approach is that when we change implementation details, no recompilation of other modules will trigger.
The down-sides are:
- Performance is sacrificed due to forcing additional heap allocations
(for the
Impl
object) and additional indirections. - Boiler-plate, public methods of the class just call the same method
of the
Impl
class.
Priv
Priv
is an alternative idiom without the down-sides of
Pimpl, but also with only a part of its benefit, as we do declare the
private members normally:
class SpamFilter
{
public:
(const std::set<std::string>& forbiddenWords);
SpamFilter bool isSpam (const std::vector<std::string>& words) const;
private:
struct Priv;
std::set<std::string> m_forbiddenWords;
};
No private methods are declared in the .h
file.
According to this idiom they all belong to the Priv
subclass, declared in the .cpp
file:
struct SpamFilter::Priv
{
static bool isSpam (const SpamFilter&, const std::string& word);
};
The private methods turned into static methods of the
Priv
nested class, so we use them like so:
bool SpamFilter::isSpam (const std::vector<std::string>& words) const
{
for (const auto& x : words)
if (Priv::isSpam (*this, x))
return true;
return false;
}
Comparison of idioms
Boilerplate | Slowdown | Extra Recompilations | |
---|---|---|---|
Plain | None (baseline) | On any change | |
Interface | +37% | less than 1% | When the interface changes |
Pimpl | +53% | 10% | |
Priv | +13% | None | Also when private members change |
The comparison numbers are based on a simple benchmark
(run-time was measured using time
on a 2020 M1 Macbook
Pro)
Should one use any of these idioms just to reduce compilation times? As C++ already has plenty of boiler-plate with header files, I'd be relunctant to add more. And why are we even using C++ in the first place if not to achieve the best run-time performance? Therefore I would prefer not to use the Pimpl idiom, and consider Priv over it, but also after exausting any other approaches to reduce compilation times without any weird workaround (like using forward declarations and include-what-you-use).
Notes
- The Priv idiom as presented here is an improvement over my original formulation, and was suggested by 9cantthinkofgoodname on reddit
- This post was written in response of Pimpl being used in the code-base of Pajam, and I intend to present it to my colleagues to discuss the trade-offs of this idiom. Will update on the results!
- Regardless of whether it uses the optimal internal implementation idioms, Pajam is really cool! If you want to jam musically with your remote friends, be sure to try it out!
- Title image credit: CoolClips.com
- r/cpp discussion