C++ Class Design


This was originally written for CVu magazine.


One of the things that you are told when you first start learning about classes in C++ is to put the data in the private/protected part of the class and write functions to access and set the data. This is all well and good as far as it goes.

Unfortunately, it often goes too far, and you get horrendous classes with (say) 10 items of data and then 20 functions of the form ReadXX()/SetXX() to read and set those data items. Really, you might as well have made those data items public for all the good the data 'hiding' did.

In general the data should be set during the construction of the object. It should really only be changed by the object itself as a result of a high level operation.

What does this mean in practice? Lets take an example of a customer account record as defined in the class below (I've removed a bunch of extraneous stuff from this, so that we can see the essentials).

typedef int UID_t;

class Account
{
protected:
long offset; // Offset into accounts file
int status; // The account current status
char name[MAX_USER_NAME]; // Account name
UID_t uid; // Player's user id
 
// more stuff
 
public:
Account();
Account(int fd);
 
UID_t UID() { return(uid); }
int Status() { return(status); }
const char *Name() { return(name); }
bool IsNew() { return((offset == -1L) ? true : false); }
bool Save(int fd);
 
// more stuff

};

The thing we want to look at is how we would save this record out to file. The first method would be for the object that is holding the list of records to ask the account for its offset into accounts file and then save the record to that offset. You would need to, at the least, provide a function that returned the offset.

Frankly this is all pretty undesirable - you are exposing the inner workings of the Account class to other classes, even if formally the data is hidden.

A much better solution is for the Accounts class to have a Save() member function of its own. The Save() function will itself make use of offset to put its data into the file, and if you later change the way you do things, you won't have to track down all the places that save out records to disk and alter them.

There is an interesting wrinkle about this class. If the record is a new one - i.e. it hasn't been written out to disk yet - then the offset is set to -1L by the constructor.

Now, the object that holds all these records needs to know if this is a new record or not. So, in spite of everything, the first cut through this code did indeed have a read function that read the value of offset. It was only when I reviewed the code that the absurdity of the whole thing struck me.

The calling function doesn't give a hoot what the value of offset is - it just wants to know if this is a new account. Hence the boolean IsNew() function. This inline function currently tests the value of offset to see if it is -1L and reports accordingly. But the nice thing is that if we decide to change the way we mark a record as new, then all we need to do is to rewrite the IsNew() function, nothing else in the program is affected!

The more eagle eyed among you will have noticed that there are some inline accessor functions in this class, and you may be wondering how I justify them. Well, really it is a judgement call, and the longer you are programming, the easier it will become. (Note that there are no functions to set the individual data members, though.)

We can, however, draw out a guideline by looking at the difference between the offset data member, which doesn't have an accessor, and the name data member, which does.

The difference lies in the fact that the name of an account is a 'real life' attribute of an account, whereas the offset is merely an artefact of the way we have organised the account data on file. The name of the account is meaningful outside of the class, whereas offset isn't.

That's not to say that all the 'real life' members should be exposed in this way - you still need to be careful. In this case I need to know the name and the uid so I can build indices with them as keys (STL rules OK!). On the other hand it is very difficult to think of any justification for exposing members that are part of the internal plumbing outside of the class.

Not, of course, that I wish to be dogmatic about this :)

Alan Lenton


Read more technical topics

Back to the Phlogiston Blue top page


If you have any questions or comments about the articles on my web site, click here to send me email.