Page 1 of 1

PIMPL using std::unique_ptr with incomplete types (C++)

Posted: Wed Apr 11, 2012 8:15 pm
by fips
I've been using the PIMPL idiom with great success in various scenarios, ranging from minimizing dependencies and speeding up compilation to hiding platform specific types on the implementation side. For example, all my OpenGL resources like Vertex Buffers, Textures, etc. lives peacefully behind PIMPL. The great thing about implementing PIMPL using std::unique_ptr is the fact that it can handle incomplete types. However, there's something one should be aware of. According to the rules described here, the type that uses PIMPL needs to define a destructor (with its implementation in CPP), otherwise it doesn't compile.

Below, you can find an example of a std::unique_ptr-PIMPled type with move semantics, which shows what I tend to use most of the time:

foo.h

Code: Select all

/*
(c) 2012 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=715
*/

#ifndef FS_HEADER__foo_h__GUARD
#define FS_HEADER__foo_h__GUARD

#include <memory>

class Foo // non-copyable, movable, PIMPLed type
{
 public:

    Foo();
    ~Foo(); // DTOR required when PIMPL uses 'std::unique_ptr'!

    Foo(Foo&& rhs); // move constructor
    Foo& operator=(Foo&& rhs); // move assignment

    void bar();

 private:

    Foo(const Foo&); // non-copyable ('= delete' in C++11)
    Foo& operator=(const Foo&); // non-copyable ('= delete' in C++11)

    // a complete type is not required in the case of 'std::unique_ptr',
    // however, 'Foo' needs to define a DTOR with its implementation in
    // CPP, otherwise it doesn't compile (note that this is not required
    // in the case of 'std::shared_ptr')

    class Impl;
    std::unique_ptr<Impl> _impl;
};

#endif // FS_HEADER__foo_h__GUARD
foo.cpp

Code: Select all

/*
(c) 2012 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=715
*/

#include "foo.h"
#include <iostream>

class Foo::Impl
{
 public:

    Impl() {}
    void bar() { std::cout << "Foo::Impl::bar();\n"; }

 private:

    Impl(const Impl&); // non-copyable ('= delete' in C++11)
    Impl& operator=(const Impl&); // non-copyable ('= delete' in C++11)
};

// forwards:

Foo::Foo() : _impl(new Impl) {}
Foo::~Foo() {} // required by 'std::unique_ptr'!

Foo::Foo(Foo&& rhs) : _impl(std::move(rhs._impl)) {}
Foo& Foo::operator=(Foo&& rhs) { _impl = std::move(rhs._impl); return *this; }

void Foo::bar() { _impl->bar(); }
main.cpp

Code: Select all

/*
(c) 2012 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=715
*/

#include "foo.h"

int main()
{
    Foo foo;
    foo.bar();

    Foo bar = std::move(foo); // moves, steals '_impl' from 'foo'
    bar.bar();

    //Foo baz = bar; // DOESN'T COMPILE!, 'Foo' is non-copyable
}

// output:
// Foo::Impl::bar();
// Foo::Impl::bar();