The Well-Crafted Class in C++: Part 1
In any project, the highest priority is to get the job done. Rather than being pretty, rather than being complete, the code must first work.
So when we (as programmers) create classes for use in custom applications, we tend to cut corners. This is not a good thing, but we have all done it. We leave out features that perhaps should belong to that class, or we skimp on activities such as testing and documentation (especially the latter). This is not necessarily carelessness; it's often the pressure of deadlines.
This is in contrast to our internal urge, which is to make a class complete and robust. A surprising number of us even enjoy documentation if we are proud of the work we have done on the code. The hacker's instinctand I use the word hacker in the classic senseis to create well-rounded, general-purpose tools. In this article, I will indulge that instinct.
After all, the class isn't always tangential to our purposes. What if we want to distribute the class or library itself? In that case, this piece of code is our main purpose.
What I want to discuss here is a set of guidelinesan incomplete set, of course. It is more nearly complete in terms of interface design; much less so in terms of implementation. If you want more complete advice, you should be looking in books, not just articles. Read materials by Bjarne Stroustrup himself if you want to go to the source or if you're heavily into theory; and read Scott Meyers if you're a little more of a pragmatist. Both writers are excellent.
What's more, I have no intention of teaching C++ or object-oriented programming here. If you have no idea what a virtual function is, or how inheritance works, refer to a tutorial.
Some of what I say will be common sense; other parts will be controversial. In no sense is it a set of rigid inflexible rules or a checklist we should follow mindlessly. My hope is that this material will help to jog your memory ("Oh yes, I forgot to do such-and-such"), will help correct little defects and add useful features, and will ultimately result in a better piece of code. If you remember the song a few years ago that said, "wear sunscreen," you already know the kind of advice I am offering and the spirit in which you should take it.
The question then is this: What makes a good class in C++? I can't answer this simply or directly. Let me break it down into a set of questions and decisions that a programmer should address.
First of all, is there any way you can leverage existing knowledge from the industry and the community? If there is a relevant, well-known design pattern, code consciously according to that pattern. A common example is the Model-View-Controller pattern (MVC), in which the internal data representation is kept separate from the external view of the data. This crops up in everything from text editors to web-based shopping carts. A good understanding of these frequently recurring patterns can act as a kind of mental amplifier. I recommend the modern classic Design Patterns by the "Gang of Four" (Erich Gamma et al, Addison-Wesley, 1995). This book can serve you well, especially coding in a static language such as C++ or Java.
Can you make your implementation easier by using the Standard Template Library (STL)? Then don't be afraid to do so (unless some of your users have older compilers without STL). If you reinvent the wheel, do it only for a good reason. Do you need to make internal use of a stack or queue? Then use an existing container class from the STL instead of creating your own.
The next question I would ask myself is: Why is this a class in the first place? Bjarne Stroustrup identifies a kind of class which he calls a concrete data typea data type that is created for convenience, but doesn't necessarily fit into an inheritance hierarchy. In OOP terms, we would say that its chief reason for being is encapsulation. An example is a date class that I wrote several years ago (for handling times and dates in a high-level, object-oriented fashion). Such a class doesn't inherit from anything else; and we normally don't expect it to have child classes, either. Always ask yourself whether this simple case holds true. In short, don't complicate a design unnecessarily.