GotW #100 demonstrated the best way to express the Pimpl idiom using only standard C++11 features:
// in header file class widget { public: widget(); ~widget(); private: class impl; unique_ptr<impl> pimpl; }; // in implementation file class widget::impl { // ::: }; widget::widget() : pimpl{ new impl{ /*...*/ } } { } widget::~widget() { } // or =default
Guru Question
Is it possible to make the widget code easier to write by wrapping the Pimpl pattern in some sort of library helper? If so, how?
Try to make the widget code as convenient and concise as possible to write, with any compiler-generated semantics either correct by default or producing compile-time errors if the widget author forgets to write them.
[Update: Removed move operations from the basic pattern. Since not all Pimpl’d types need to be move-aware, it’s not really part of the core pattern.]
How about this… CRTP as others have said, with a Pimpl base class template split in two files, one to declare it and one to define it out of line. Should look something like this:
pimpl.hpp:
#include <memory>
template<typename Derived>
class Pimpl_base
{
public:
template<typename… Args>
Pimpl_base(Args&&… args);
virtual ~Pimpl_base();
Pimpl_base(Pimpl_base const&);
Pimpl_base(Pimpl_base&&);
Pimpl_base& operator=(Pimpl_base const& rhs);
Pimpl_base& operator=(Pimpl_base&&);
protected:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
pimpl_impl.hpp:
template<typename Derived>
template<typename… Args>
Pimpl_base<Derived>::Pimpl_base(Args&&… args)
: m_impl(new Impl(args…))
{
}
template<typename Derived>
Pimpl_base<Derived>::Pimpl_base(Pimpl_base<Derived> const& src)
: m_impl(new Impl(*src.m_impl))
{
}
template<typename Derived>
Pimpl_base<Derived>::~Pimpl_base() = default;
template<typename Derived>
Pimpl_base<Derived>::Pimpl_base(Pimpl_base<Derived>&&) = default;
template<typename Derived>
Pimpl_base<Derived>& Pimpl_base<Derived>::operator=(Pimpl_base<Derived>&&) = default;
template<typename Derived>
Pimpl_base<Derived>& Pimpl_base<Derived>::operator=(Pimpl_base<Derived> const& rhs)
{
*m_impl = *rhs.m_impl;
}
And the user class would be split into declaration…:
A.hpp:
#include “pimpl.hpp”
class A
: private Pimpl_base<A>
{
public:
A(int value);
void speak() const;
};
and definition:
A.cpp:
#include “A.hpp”
#include “Pimpl_impl.hpp”
#include <iostream>
template<>
struct Pimpl_base<A>::Impl
{
Impl(int value)
: value(value)
{
}
int value;
};
template class Pimpl_base<A>;
A::A(int value = 0)
: Pimpl_base(value)
{
}
void A::speak() const
{
std::cout << m_impl->value << std::endl;
}
Finally, the client of class A doesn’t see the details of the Pimpl_base implementation.
client.cpp
#include “A.hpp”
int main()
{
A a1(1), a2(2);
a1.speak();
a1 = move(a2);
a1.speak();
A a3(move(a1));
a3.speak();
A a4(a3);
a4.speak();
A a5;
a5 = a3;
a3.speak();
return 0;
}
BTW In order to display < &rt; chars in GotW comments I use special HTML chars: < and &rt;
I tried with CRTP but always end up with serious trade-offs. Now I would go in below direction:
Pimpl wrapper (it need more work more on make it more secure and maybe add more pointer semantics).
This is more a prototype than final solution:
template< typename T >
class pimpl_wrapper
{
std::unique_ptr< T > pimpl;
public:
pimpl_wrapper() : pimpl(new T) {}
T *operator->() const { return pimpl.get(); }
};
The widget class will look like this:
class widget
{
public:
widget();
~widget();
widget( widget&& );
widget& operator=( widget&& );
private:
class impl;
pimpl_wrapper<impl> pimpl;
};
and in widget you can call pimpl just like direct pointer (suppose both widget and impl have doWork method):
void widget::doWork()
{
pimpl->doWork();
}
Hello.
There is a design pattern named Bridge. It is well-suited for this task.
This reminds me of Vladimir Batov’s Pimpl library:
http://drdobbs.com/cpp/205918714?pgno=1
Hi, I don’t feel skilled enough to propose a possible implementation but I follow the boost mailing list and I know there is a proposed library for providing the pimpl idiom : https://github.com/sean-/boost-pimpl
It’s in the review queue but don’t have a review manager yet.
I believe it’s a C++03 implementation and I’m guessing that a C++11 implementation would certainly be simpler to express. But as it have been worked by someone who did thought a lot about the problem, I think it might be a good source to check first. Maybe it would be easily simplified by converting the current code to C++11 idioms?
Perhaps it is easier and safer to write if you don’t have to use it.
A pure interface can be used to refer to an unknown implementation just as easily and has full compiler support. In turn, to make this easier, an (abstract) factory of some sort could be used.
Perhaps a discussion of factory patterns in C++11 would be useful too.
I also think it would be possible to use CRTP with private inheritance. However I can’t see any benefits in doing so. The amount of code required to do so would probably be more than the non-librarized version.
And my attempt at escaping angle brackets with ‘\’ fails :(
The template argument to pimpl_base is supposed to be ImplT, and widget inherits from pimpl_base with an argument of widget_impl. The unique_ptr is templated on ImplT.
I think CRTP (the Curiously Recurring Template Pattern) is what Herb is looking for. We might be able to write a pimpl base class that looks something like this:
template
class pimpl_base
{
protected:
pimpl_base(ImplT *p) : pimpl(p) {}
unique_ptr\ pimpl;
};
And it would be used like this:
struct widget_impl;
class widget : private pimpl_base\
{
public:
widget();
~widget();
};
If you don’t declare some constructor, then you get errors about a lack of a suitable default constructor.
If you don’t declare the destructor, you get errors about the use of an undefined type (widget_impl in this case).
You don’t have to declare move or copy operations unless you want them. If you want them, then you will need to out-of-line them, and =default them in your cpp. If you try to use those operations, then the compile will fail.