15 January, 2007

Private Classes in C++

For all you object-oriented software developers using C++ out there I hope this post will be of interest.

Without getting into specifics; there are numerous occasions when an abstraction spans a single class and to preserve encapsulation you may choose to define a private class (an alternative to defining a friend class). I generally have chosen to embed the private class declaration exhaustively in the public class'es private region. Recently however I've encountered on a couple other options that I thought would be interesting to share.

Option 1
Let's start with the most straightforward method; embedding the private class in the public classes private region.


// -- file: SomeClass.h --
#ifndef SOMECLASS_H
#define SOMECLASS_H

class SomeClass {
public:
SomeClass();
virtual ~SomeClass();
protected:
private: //-- methods and private classes --
class SomePrivateClass {
public:
SomePrivateClass();
~SomePrivateClass();
void doSomethingInteresting();
protected:
private:
};
private: //-- attributes --
SomePrivateClass myPrivates_;
};

#endif

// -- file: SomeClass.cpp --
#include "SomeClass.h"
#include <stdio.h>

SomeClass::SomeClass():myPrivates_() {
myPrivates_.doSomethingInteresting();
}

SomeClass::~SomeClass() {
}

SomeClass::SomePrivateClass::SomePrivateClass() {
}

SomeClass::SomePrivateClass::~SomePrivateClass() {
}

void SomeClass::SomePrivateClass::doSomethingInteresting() {
printf("(%s:%d) doing something interesting\n",__FILE__,__LINE__);
}


One of the disadvantages of this approach is that the header file becomes quite large to accommodate the multiple class definitions. Additionally, fully declaring the private class smack dab in the middle of the private region of the public class can easily give the reviewer/maintainer a headache. Indentation is pretty much your only indicator of what class you are currently looking at. Imagine your current page shows 3 method declarations (w/ documentation) you can easily lose track of what class the methods are associated with.

Option 2
The second option is similar to option 1 in that both class declarations are defined in the header, but note that the public classes instance of the private class is no longer contained by value but instead contained by reference. Since the private class declaration is not complete prior to declaring an instance of the class the size of the private class is unknown, therefore an instance cannot be defined. A pointer to an object however is allowed because a pointer is of fixed size (oftentimes the size of an integer). As you are probably aware, pointers are evil and the definition of an unmanaged pointer is even more evil. If you don't know why pointers are evil I'd highly recommend reading Marshall Cline's FAQ. I've defined a dumb pointer solely for ease of demonstration, I'd certainly recommend a managed pointer to avoid resource leaks.


// -- file: SomeClass.h --
#ifndef SOMECLASS_H
#define SOMECLASS_H

class SomeClass {
public:
SomeClass();
virtual ~SomeClass();
protected:
private: //-- methods and private classes --
class SomePrivateClass;
private: //-- attributes --
SomePrivateClass* myPrivates_;
};

class SomeClass::SomePrivateClass {
public:
SomePrivateClass();
~SomePrivateClass();
void doSomethingInteresting();
protected:
private:
};

#endif

// -- file: SomeClass.cpp --
#include "SomeClass.h"
#include <stdio.h>

SomeClass::SomeClass():myPrivates_(new SomePrivateClass()) {
myPrivates_->doSomethingInteresting();
}

SomeClass::~SomeClass() {
}

SomeClass::SomePrivateClass::SomePrivateClass() {
}

SomeClass::SomePrivateClass::~SomePrivateClass() {
}

void SomeClass::SomePrivateClass::doSomethingInteresting() {
printf("(%s:%d) doing something interesting\n",__FILE__,__LINE__);
}


While this option eases navigation of what class you are currently looking at it still suffers from a potentially large header file. Additionally, since the private class declarations are located in the header file, changes to the private class due to implemenation changes will result in header compile-time dependencies.

Option 3
This option is similar to option 2 except the private class is fully declared and defined in the cpp file; seperating the implementation from the public interface.
This is formally known as the Pimpl Principle.


// -- file: SomeClass.h --
#ifndef SOMECLASS_H
#define SOMECLASS_H

class SomeClass {
public:
SomeClass();
virtual ~SomeClass();
protected:
private: //-- methods and private classes --
class SomePrivateClass;
private: //-- attributes --
SomePrivateClass* myPrivates_;
};

#endif

// -- file: SomeClass.h --
#ifndef SOMECLASS_H
#define SOMECLASS_H

class SomeClass {
public:
SomeClass();
virtual ~SomeClass();
protected:
private: //-- methods and private classes --
class SomePrivateClass;
private: //-- attributes --
SomePrivateClass* myPrivates_;
};

#endif

// -- file: SomeClass.cpp --
#include "SomeClass.h"
#include <stdio.h>

// note the definition of private class prior to declaration of
// instance of class
class SomeClass::SomePrivateClass {
public:
SomePrivateClass();
~SomePrivateClass();
void doSomethingInteresting();
protected:
private:
};

SomeClass::SomeClass():myPrivates_(new SomePrivateClass()) {
myPrivates_->doSomethingInteresting();
}

SomeClass::~SomeClass() {
}


// private class definitions
SomeClass::SomePrivateClass::SomePrivateClass() {
}

SomeClass::SomePrivateClass::~SomePrivateClass() {
}

void SomeClass::SomePrivateClass::doSomethingInteresting() {
printf("(%s:%d) doing something interesting\n",__FILE__,__LINE__);
}




I don't know if I strongly recommend any of these methods as of yet. I've currently discovered the latter two options and am slowly forming opinions on them. Regardless of my preferences it's nice to have options.

No comments: