A very convenient tecnique in c++ programming is the one known with many names: "d-pointer" (found in Qt/KDE contexts), shadow pointer, "pimpl", opaque pointer. Basically the idea is to hide all the private details of a class in a forward declared private pointer, which will be the only real private member of the class. Since this member will never change, this will guarantee binary compatibility among different versions of a library.

But there are other advantages in using d-pointers: one is compilation speedup during development (usually if you change a private member of a class, the header changes, and you have to recomplile all units using that header, while with d-pointer you change only the .cpp file), and another is code cleanup: you will have very compact header files, describing your class public interface without private stuff pollution. Also, sometimes you may want to add your d-pointer definition in a separate header file, ending up with three well defined files:

  • myclass.h: class declaration, only public stuff, without private stuff
  • myclass_p.hpp: private class declaration: only private stuff, no implementations
  • myclass.cpp: only implementations

The classical approach is to create a plain raw pointer to a forward declared class, initialize it in the constructor, and delete it on the destructor. A nice addition is to have the private class be a nested type, so that you can avoid polluting your IDE class list.

// Header file
class MyClass {
public:
    MyClass();
    ~MyClass();
private:
    class Private;
    friend class Private;
    Private *d;
};
// cpp file

class MyClass::Private {
public:
    int my_private_int;
};

MyClass::MyClass() : d(new Private) {
    d->my_private_int = 3;
}

MyClass::~MyClass() {
    delete d;
}

This is the simplest approach of course, but it might lead to a memory leak: if you forget to add a destructor, then your private class will never be deleted. A smart solution is to use a smart pointer, like c++11 std::unique_ptr, or boost scoped_ptr. Note that you still have to add a destructor to your class, but if you don't, at least you'll get a compiler error to warn you.

Though the unique_ptr approach helps, there's still a lot to be improved, particularly if you're forwarding your class parameters to the d-pointer.

// Header file
#include <memory>
class MyClass {
public:
    MyClass(int a, const std::string &b, AnotherClass *c);
    ~MyClass();
private:
    class Private;
    friend class Private;
    std::unique_ptr<Private> d;
};
// cpp file

class MyClass::Private {
public:
    Private(int a, const std::string &b, AnotherClass *c);
    int a;
    std::string b;
    AnotherClass *c;
};

MyClass::Private::Private(int a, const std::string &b, AnotherClass *c) : a{a}, b{b}, c{c} {
}

MyClass::MyClass(int a, const std::string &b, AnotherClass *c) : d(new Private(a, b, c)) {
}

MyClass::~MyClass() {
}

As you can see, we have two constructors in the cpp file, with almost the same signature: one in the main class, and another one in the private class. If you cnange one parameter in the main class constructor, you'll probably have to forward all this changes to all the chain.

I tried to came with a solution to remove pretty much all the boilerplate code I could, including the d-pointer declaration, and the private class constructor call. It is c++03 compliant (I created a helper class similar to std::unique_ptr), but it works best with c++11 and 14, because it can use uniform initialization to avoid creating a constructor at all. Since I use a struct, and not a class, all members of the d-pointer are automatically public, and this also allows the uniform initialization to work without constructors.

// dptr.hpp - GuLinux DPTR (pimpl) Manager
#ifndef CPP_DPTR_H
#define CPP_DPTR_H

#if __cplusplus >= 201103L
// Use std::unique_ptr instead of custom ptr class
// Use new uniform initializer to avoid unnecessary constructor definition
#include <memory>
template<typename T> using UniqueDPTR = std::unique_ptr<T>;
#define dptr(...) d(new Private{__VA_ARGS__})

#else // __cplusplus
// Simple std::unique_ptr replacement class
// Doesn't aim to be a fully featured unique_ptr, but just a very basic ptr wrapper with automatic deletion for dptr.
template<typename T> class UniqueDPTR {
public:
    UniqueDPTR(T *t) : ptr(t) {}
    ~UniqueDPTR() { delete ptr; }
    T *get() const { return ptr; }
    T &operator*() const { return *get(); }
    T *operator->() const { return get(); }
private:
    T *ptr;
    UniqueDPTR(UniqueDPTR &); // Disable copy constructor
    UniqueDPTR &operator=(const UniqueDPTR &); // Disable assignment
};

#define dptr(...) d(new Private(__VA_ARGS__))
#endif // __cplusplus

#define DPTR_IMPL(_class) struct _class::Private
#define DPTR struct Private; friend struct Private; const UniqueDPTR<Private> d;

#endif // CPP_DPTR_H

This is an example in c++11: as you can see the code is much more compact now.

// Header file
#include "dptr.hpp"
class MyClass {
public:
    MyClass(int a, const std::string &b, AnotherClass *c);
    ~MyClass();
private:
    DPTR // Use the DPTR macro to forward declare the class, and to declare the unique_ptr.
};
// cpp file

DPTR_IMPL(MyClass) { // Friendly macro for declaring the struct MyClass::Private
    int a;
    std::string b;
    AnotherClass *c; // Skip the "public: " declaration, and the constructor declaration: we're using uniform initialization
};

MyClass::MyClass(int a, const std::string &b, AnotherClass *c) : dptr(a, b, c) {
// the dptr(...) macro automatically initializes the unique_ptr,
// creating the private class, and uses uniform initialization to initialize the parameters.
}

MyClass::~MyClass() {
}

An updated version will be on my github GuLinux-Commons repository, in the c++ directory. More about shadow pointers on Wikipedia

0.0

Add a comment

Next Post Previous Post