Archive: January 26, 2003

<<< January 25, 2003

Home

January 27, 2003 >>>


How to Write C++ Classes

Sunday,  01/26/03  10:14 AM

If you're programming in 2003, you're most likely coding in either C++ or Java.  These languages are quite similar syntactically, and provide a solid, well-defined way to package functionality in objects.  They also share a common defect, they make it difficult to hide interface details from implementation details.  I have the solution - read on!

The Problem

In a typical class, you have a header file <class>.h and a code file <class>.cpp.  The header contains the class definition.  The class's public properties and methods define the interface details of the class.  This is the information users of the class need.  The class's private properties and methods define the implementation details of the class.  This information is only used in the code file, it is not needed by any user of the class.  But, because both sets of properties and methods are in the same header file, all users of the class "see" the implementation details.  This is the defect.

Before prescribing a solution, let me explain why this is a defect.  First, it is ugly, and by W=UH this means it is wrong.  Second, whenever the implementation data are changed, the header changes.  This causes all users of the class to require recompilation.  But changing the implementation should never affect any users of the class!  Third, changing the private properties and methods may change the size of the class's data.  This implementation-only change can affect users.  Consider the following example:

_____ myclass.h _____
class   myclass {
private:
        int     theInt;
public:
        myclass(void);
int     getInt(void);
void    putInt(int theInt);
        };

_____ mycaller.cpp _____
...
void    myfunc(void) {
myclass localInt;
int     stackInt;
...

This shows a simple class which encapsulates an integer, and a simple caller which instantiates it.  Let's say we change the class slightly, as follows:

_____ myclass.h _____
class   myclass {
private:
        int     theInt;
        int     counter;
public:
        myclass(void);
int     getInt(void);
void    putInt(int theInt);
        };

The new implementation datum is highlighted in green.  Even though this is only a change to the implementation, it will break the caller!  Because the size of the myclass object has changed, the location of stackInt will change.  Not only will every user be recompiled because the class header changed, but every user has to be recomplied, because the size of the object has changed.  All for an implementation-only change.  A defect indeed.

The Solution

Okay, now on to the solution.  The crux of the problem is that the implementation details (the private properties and methods) are defined in the class header.  So let's pull them out of the header, shall we?  Instead of defining private properties and methods in the class header, let's create a new internal class to "package" the implementation details.  This class will be defined in the code module for the class.  Only a pointer to the internal class will appear in the header.  Here's the class header with this organization:

_____ myclass.h _____
class   iclass;            // warn compiler iclass is a class

class   myclass {
private:
        iclass  *icp;      // internal data object pointer
public:
        myclass(void);
int     getInt(void);
void    putInt(int theInt);
        };

As you can see, there is only one private datum defined in the class header, a pointer to the internal class.  Here's the code module for the class:

_____ myclass.cpp _____
...
#include "myclass.h"       // external class definition

class   iclass  {          // internal implementation class
public:                      // everything in iclass is accessible
        int     myInt;
        };
...
myclass::myclass (void) {  // class constructor
        icp = new iclass;  // instantiate internal object
        ...
}
...
myclass::~myclass () {     // class destructor
        ...
        delete icp;        // destroy internal object
}
...
int myclass::getInt (void) {   // class method
        return (icp->theInt);  // access internal data though pointer
}
...

Note that the internal class is defined right in the code module, there is no need to define it in a separate header, because this is the only place it will be used.  If we want to add a new datum to the implementation, we just go ahead and do it in the code module:

_____ myclass.cpp _____
...
#include "myclass.h"       // external class definition
...
class   iclass  {          // internal implementation class
public:                      // everything in iclass is accessible
        int     myInt;
        int     counter;
        };

myclass::myclass (void) {  // class constructor
        icp = new iclass;  // instantiate internal object
        icp->counter = 0;  // can perform initialization here...
        ...
}
...
myclass::~myclass () {     // class destructor
        ...
        delete icp;        // destroy internal object
}
...
int myclass::getInt (void) {   // class method
        icp->counter++;        // internal-only data
        return (icp->theInt);  // access internal data though pointer
}
...

Again, the changes are highlighted in green.  Only the code module is affected; the class header did not change, and none of the users of the class have to be recompiled.  Isn't this pretty?  It must be right :)

This technique is sometimes called a "Cheshire Cat" class, after John Carolan, an early C++ pioneer.  The lone private pointer to the internal data object is called "the smile", of course...

More Advanced Example

The example above was pretty simple, but more advanced stuff can be done with an internal class.  In particular, this class can have internal-only methods.  Here's an example of this in action...  the external class remains the same as in the examples above, so all this only affects the code module.

_____ myclass.cpp _____
...
#include "myclass.h"       // external class definition

class   iclass  {          // internal implementation class
public:                      // everything in iclass is accessible
        iclass(void);      // internal class can have constructor
int     getInt(void);      // as well as other methods...

        int     myInt;
        int     counter;
        };

iclass::iclass (void) {    // internal class constructor
        counter = 0;       // perform initialization here...
}

int iclass::getInt (void) {  // internal class method
        counter++;
        return (myInt);
}

...
myclass::myclass (void) {  // class constructor
        icp = new iclass;  // instantiate internal object
        ...
}
...
myclass::~myclass () {     // class destructor
        ...
        delete icp;        // destroy internal object
}
...
int myclass::getInt (void) {   // class method
        return icp->getInt();  // access internal data though methods
}
...

In this example, a constructor for the internal class has been added, as well as a method.  Because the internal class has addressability to all class data (by definition, since the class is used to encapsulate those data), there is no reason not to use internal class methods for all "local" subroutines, instead of private methods of the external class.  As with private data, this has the dual advantages of "hiding" the implementation details, since they are not in the class header, and isolating users of the class from any changes to those details.

This organization has other benefits as well.  Suppose the class uses another class internally for implementation.  For example, say the myclass defined above instantiated a cString object.  In the standard organization, this would mean placing a #include "cString.h" in the myclass header, even though the cString object is only used internally.  This exposes any users of myclass to changes in cString!  By separating the internal data into an internal class only defined in the code module, the #include "cString.h" is only needed in the code module.  All users of myclass remain blissfully unaware that a cString is being used.  Now if cString changes only the code module for myclass would have to be recompiled, and that makes sense, because myclass uses a cString.

There you have it - a clean solution to a troublesome problem.

Caveat: Some readers have pointed out that there is overhead inherent in this technique.  For each object instantiation, there is another internal object instantiated, and the data required by each object is increased by the size of the private pointer plus the allocation overhead for another object (typically 16 bytes).  There are also extra calls to new and delete for each object allocation and destruction.  This is all true.  For tiny objects which are allocated and destroyed rapidly, the overhead may be unacceptable.  In most cases this overhead disappears into the noise, and the added elegance and maintainability are well worth it.

[ Later - for more please see How to Write C++ Classes II... ]

[ Later still - I am indebted to several readers for comments and suggestions which improved and simplified this technique, including Eric Holm, Klitos Kyriacou, and Horacio Peña.  Thanks! ]

 

 

Sunday,  01/26/03  01:02 PM

When I'm coding, I usually listen to music.  I either listen to MP3s, bring up Live365, or use Real Player's Radio feature.  The last week or so I've been listening to Jazz Network through Real Player.  No commercials, just a clean 132bps stream of beautiful jazz...  I just noticed Jazz Network is in Germany.  Wow.  All those little packets of sound are travelling deep under the Atlantic Ocean just to reach my ears.  The music industry is so screwed!  Why would anyone buy music?

 

Sunday,  01/26/03  10:27 PM

Don't know which was a worse beating, Bucs over Raiders or Kasparov over Deep Junior.  Neither was a good game, nor close.  My favorite ad was the one where Ozzie Osborne wakes up in bed with Florence Henderson, after dreaming his kids turned into the Osmonds.  Dumbest was the Bud Light upside-down drinker.  Who thought of that one?

Joel seems to have run into Murphy's Law.  Poor guy.  Everyone out there does have current backups, right?

FuturePundit: Cloning, Biotech, and the Future of the Family.  Very interesting!  An angle affecting the gene pool of the future I had not considered before...

Real Networks and Major League Baseball have announced they will collaborate to webcast virtually all MLB games next season.  At $10/month only early adopters will try it, but this is an interesting trend.  For many radio has already moved to the web, maybe TV will also?  You still have the problem that the TV is in the family room, the computer is in the office...  of course 802.11g may solve that.

 
 

Return to the archive.