Chester
 All Classes Files Functions Variables Macros Pages
Chester - d-pointers

Chester is an implementation of the Cheshire Cat idiom - also known as d-pointer, PImpl (Pointer IMPLementation), or opaqua data pointer.It hides the data of a class behind a "d" pointer.

This implementation takes away much of the bookkeeping work, like allocating and de-allocating the pointer.

All the files of Chester are copyrighted under a permissive license: Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any warranty.

So, you are free to use this implementation in any project under any kind of license as long as you do not remove my copyright notice.

Although I do not require it, I would appreciate feedback on problems, bugs, and the occasional code improvement - if you feel like it...

Different versions of Chester can be mixed for different classes, but the same version must be used both in the header file and in the implementation of the same class.

Kinds of D-Pointers

Cheshire cats or d-pointers allow you to hide the data members (and if you want some of the implementation) of a class and also allow (within some constraints) to maintain ABI compatibility (ABI=Application Binary Interface) while adding or removing hidden data members.

Chester defines three different kinds of d-pointers: non-copy, non-shared, and shared. Those three define different access patterns to the d-pointer.

Non-copy d-pointers cannot be copied directly, if you want to make your main class copyable you have to explicitly create a copy constructor and/or an assignment operator. Those should be used with main classes and data objects that cannot be copied (like Qt's QObject and its subclasses).

Non-shared d-pointers can be copied - if you copy an instance of the main class, the content of the d-pointer gets copied automatically (at least if you use the automatic copy constructor or call the copy constructor of the d-pointer). Each copy acts independently. This version produces the same behavior as if the data was defined directly in the main class instead of inside the d-pointer.

Shared d-pointers can also be copied, but copying shares the data between instances of the main class. This can be used to create interface classes in which all copied instances share their data.

All three kinds share the usage pattern. The header file and class interface declaration is declared like this (this example uses non-shared d-pointers):

//file: myclass.h
#include <DPtrBase>
//declare main class
class MyClass
{
//declare d-pointer
//declare the remainder of the class minus data
public:
MyClass();
void setInt(int);
int getInt()const;
int getIntPlusOne()const{return getInt()+1;}
};

As shown above, methods cannot be defined as inlines if the data is hidden behind a d-pointer, since this would require the compiler to know the structure of the d-pointer. However, it is possible to inline any method that does not access the d-pointer directly.

The actual implementation file then declares and defines the d-pointer class and defines all methods of the main class that need access to the d-pointer:

//file: myclass.cpp
#include "myclass.h"
#include <DPtr>
//declare internal d-pointer class
class DPTR_CLASS_NAME(MyClass):public DPtr
{
public:
int myint;
};
//define glue code
DEFINE_DPTR(MyClass)
//constructor
MyClass:MyClass()
{
d->myint = 0;
}
//a setter function (non-const)
void MyClass::setInt(int i)
{
d->myint = i;
}
//a getter function (const)
int MyClass::getInt()const
{
return d->myint;
//this code would not compile:
//d->myint = 7;
}
//example usage:
main()
{
MyClass myinst;
myinst.setInt(1);
}

Which actual kind of d-pointer behavior is declared is decided by using different macros and base classes for the d-pointer class. The argument to the DECLARE_* macro defines the name of the d-pointer variable, usually this will be "d", but you can chose any other name (except "Private" and "DPrivate", which are used as wrapper class names).

The DPTR_CLASS_NAME macro returns the class name of the d-pointer class relative to the main class, which is given as argument. The DEFINE_* macro defines the logic that automatically instantiates, deletes and copies the d-pointer when the main class is instantiated, deleted, and copied. The d-pointer class must be derived from one of the DPtr classes, since these implement some functionality complementing the DEFINE_* macro.

The header file should only include <DPtrBase> - this file contains the DECLARE_* macros, which is the only thing needed for the header. If you include any other file, this makes mixing versions of Chester impossible in some situations. The implementation file must include one of the d-pointer implementation variants.

Comparison Table

The macros, classes, #include statements above need to be replaced with different ones if you want one of the other kinds of d-pointers:

TypePointer Declaration (1)Declaration Include (1) Base Class(2, 3)Definition (2)Implementation Include (2)
Non-Shared, CopyableDECLARE_DPTR(Class)#include <DPtrBase > DPtrDEFINE_DPTR(Class) #include <DPtr >
SharedDECLARE_SHARED_DPTR(Class)#include <DPtrBase > SharedDPtrDEFINE_SHARED_DPTR(Class) #include <SharedDPtr >
Non-CopyableDECLARE_NONCOPY_DPTR(Class)#include <DPtrBase > NonCopyDPtrDEFINE_NONCOPY_DPTR(Class) #include <NonCopyDPtr >

(1) The DECLARE_* macro and declartion include directive are supposed to be used in the header file of the main class.

(2) The implementation include directive should only be used in the main class'es implementation file - use the DEFINE_* macro to create the glue code that connects the d-pointer class with the main class.

(3) The base class must be used to derive the d-pointer class in the implementation file. It contains some functionality that the DEFINE_* macro relies on.

There are three more macros that return default names for d-pointer properties:

Using Non-Shared D-Pointers

Using the code above you will get a non-shared d-pointer for your main class. The behavior of your main class will be very similar to the behavior you would get with defining the data inside the main class itself. So this is the normal choice when you transform a normal class into a d-pointer based class. The automatic copy constructor and the automatic assignment operator work exactly as would be expected from a normal class:

MyClass my1;
my1.setInt(1);
printf("my1 is now %i\n",my1.getInt()); // my1 is now 1
//create a copy
MyClass my2(my1);
printf("my2 is now %i\n",my2.getInt()); // my2 is now 1
my1.setInt(2);
my2.setInt(3);
printf("my1 is now %i\n",my1.getInt()); // my1 is now 2
printf("my2 is now %i\n",my2.getInt()); // my2 is now 3

If you want to override the copy constructor you have several choices: you can do all the copying yourself in the main class or the d-pointer class, you can use the d-pointers copy constructor or you can use the d-pointers assignment operator:

//version 1: using the copy constructor
MyClass::MyClass(const MyClass&copy)
:d(copy.d)
{
}
//version 2: using the assignment operator
MyClass::MyClass(const MyClass&copy)
{
d = copy.d;
}
//version 3: doing the copying itself in the main class
MyClass::MyClass(const MyClass&copy)
{
d->myint = copy.d->myint;
}
//version 4: d-pointer class with copy constructor:
class DPTR_CLASS_NAME(MyClass):public DPtr
{
public:
DPTR_NAME(const DPTR_NAME&copy)
{
myint = copy.myint;
}
}

It is recommended to use the copy constructor, since this most closely models the behavior of the automatic copy constructor and may save some unnecessary initialization of members. The manual copying of members is quite error prone, since it may break if you add members to the d-pointer, but forget to add them in the copy constructor of the main class or the d-pointer class.

The version of a d-pointer class above uses the DPTR_NAME macro to resolve the local name of its constructor and class name. It is also almost identical to the version that the compiler would have created automatically, but as with the custom versions in the main class above you can change some behavior here. However, since the danger exists to forget some members it is not recommended to do this. Instead it is usually much safer to use version 1 above and add custom code in the body of the copy constructor of the main class.

For the assignment operator you also have the choice of implementing it in the main class or the d-pointer class, but it is recommended to do it in the main class:

MyClass& MyClass::operator=(const MyClass&copy)
{
//use the automatic operator from d-pointer
d = copy.d;
//add custom code here...
}

This leverages the automatic assignment operator of the d-pointer class, eliminating the risk of forgetting members and still allows some customization.

Using Shared D-Pointers

When defining shared d-pointers you have to use different macros and a different base class, but otherwise things stay identical:

// file: myfile.h
#include <DPtrBase>
class MyClass
{
public:
...
};
// file: myfile.cpp
#include "myfile.h"
#include <SharedDPtr>
//define d-pointer class
class DPTR_CLASS_NAME(MyClass):public SharedDPtr
{
public:
int myint;
};
//define glue code

Instances of shared d-pointers access the same members if they have been assigned from each other:

MyClass my1;
my1.setInt(1);
printf("my1 is now %i\n",my1.getInt()); // my1 is now 1
//create a copy
MyClass my2(my1);
printf("my2 is now %i\n",my2.getInt()); // my2 is now 1
my1.setInt(2);
my2.setInt(3);
printf("my1 is now %i\n",my1.getInt()); // my1 is now 3
printf("my2 is now %i\n",my2.getInt()); // my2 is now 3
MyClass my3;
my3.setInt(99);
printf("my1 is now %i\n",my1.getInt()); // my1 is now 3
printf("my3 is now %i\n",my3.getInt()); // my3 is now 99
my3 = my1;
printf("my3 is now %i\n",my3.getInt()); // my3 is now 3

If instances are never copied or assigned they still are independent, they get linked as soon as assignment happens. Chester keeps a reference counter on the d-pointer automatically allocating and de-allocating it if necessary.

Even in the case of shared d-pointers it is sometimes desireable to create a "deep copy" - a copy of an instance that is independent from the original. This can be done by implementing a simple clone method:

MyClass MyClass::deepCopy()const
{
MyClass ret;
ret.d=d.clone();
return ret;
}
...
my3 = my1.deepCopy();
my3.setInt(100);
printf("my1 is now %i\n",my1.getInt()); // my3 is now 3
printf("my3 is now %i\n",my3.getInt()); // my3 is now 100

Please note that the clone() method of the d-pointer is called with a dot (.), not an arrow (->) - we are using a method of the pointer-wrapper itself here. Please see below for details.

Under normal circumstances the copy constructor of the d-pointer class is never used in shared d-pointers, the assignment operator is only used when creating deep copies. The rest of the time the pointer is just passed around.

Non-Copyable D-Pointers

When defining non-copyable d-pointers you have to use different macros and a different base class, but otherwise things stay identical:

// file: myfile.h
#include <DPtrBase>
class MyClass
{
public:
...
};
// file: myfile.cpp
#include "myfile.h"
#include <NonCopyDPtr>
//define d-pointer class
class DPTR_CLASS_NAME(MyClass):public NonCopyDPtr
{
public:
int myint;
};
//define glue code

You can use the non-copyable version of Chesters d-pointers if you want to prevent the user from copying instances of your main class or if you want to explicitly implement the copy constructor and assignment operator yourself. If you do implement them you will have to do this in the main class, since the d-pointer-wrapper itself has a built-in barrier to copying - only its default constructor is usable.

This version is meant for d-pointers serving classes that are inherently un-copyable, like Qt's widgets.

Deep Magic: the Inner Workings of Chester

The DECLARE_* macro of Chester actually defines two internal classes for its purposes: MyClass::Private and MyClass::DPrivate. The MyClass::Private class is the one you define to contain the data and the one that you access through "d->". The MyClass::DPrivate class contains the wrapper code that automatically instantiates and deletes the instance of MyClass::Private. The member variable "d" is actually an instance of DPrivate - however the "->" operator of that class returns a pointer of Private, so that it appears to be a normal pointer to Private. But since no dereference operator (*) is defined you cannot directly overwrite or delete the instance of Private - this serves as a barrier against accidental deletion or non-deletion of the d-pointer (of course you can still get at the pointer with some fancy casting).

A simplified version of the expanded DECLARE_* macro would look like this:

class MyClass
{
//DECLARE_*DPTR(d):
class Private; //forward declaration of Private
class DPrivate
{
public:
DPrivate(); //constructor, see below
~DPrivate(); //destructor, see below
const Private* operator->()const; //access to the pointer, const
Private* operator->(); //access to the pointer, non-const
private:
Private *inner; //actual pointer
};
DPrivate d; //instance of the d-pointer-wrapper
//end of macro
public:
MyClass(); //...
};

The DEFINE_* macro then implements the methods declared above:

//DEFINE_*DPTR(MyClass):
MyClass::DPrivate::DPrivate()
{
inner = new MyClass::Private;
}
MyClass::DPrivate::~DPrivate()
{
delete inner;
}
MyClass::Private* MyClass::DPrivate::operator->()
{
return inner;
}
const MyClass::Private* MyClass::DPrivate::operator->()const
{
return inner;
}

So when accessing "d" directly we access the wrapper class (which does not offer much of an interface), and when accessing it through "d->" we access members of the inner member of the wrapper instead of the wrapper itself.

There are two versions of the "->" operator. The first one without const is used when the d-pointer is written to, the second version with const is used for pure read operations and inside const methods of the main class. Having those two versions ensures that the members of the d-pointer adhere to the const declarations of the main class, otherwise it would be possible to write to members even if it happens inside a const method.

The differences between the various versions of the macros are in details not shown above - copy constructors, and assignment operators.

A Word about ABI

If you want to use d-pointers to maintain ABI (Application Binary Interface) compatibility over several versions, you also have to adhere to a few more rules. While adding and removing data members is the most obvious cause of ABI incompatibility, some other changes also influence it. For example:

For a more complete article on the topic see for example: http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++