As some of you know I now present some of Richford's courses for serious programmers converting to C++. One of the benefits of such involvement is being made aware of the problems that face experienced programmers when they first start writing their own classes. The following are a few thoughts based on my observations.
Never Make Data public
I know it is possible to make data public but doing so reveals a lack of understanding of the nature of 'data hiding' and object-orientation. Even making a single data member public breaches your data security and allows others to get at your data. More importantly it has ensured that any time you use such a class you will need to check all your source code when you decide to change your data structure. Do I hear you murmur that you will never want to make such a change? You may be right, but why take the risk? One of the key advantages to OOP is that you get a payback for the resources invested in writing re-usable code when you come to maintain the packages using it.
Keep to Simple Data Structures
A class should do one clearly defined thing - usually provide a new user-defined type. If you feel that the best way of implementing some aspect of your class is via some other data-structure (eg a linked list) implement such a class separately.
I recently found myself trying to implement a linked list class without first defining a node class. Madness, utter madness! When I realised my insanity it was easy to go back, write my node class and then use it for my linked list class.
When programmers first meet inheritance it is often like a new shiny toy and they want to use it all the time. Inheritance is an excellent tool when applied properly but you should not get in the habit of warping your view of the world in order to use it. Such a mindset leads not only to over-use of simple inheritance but creates a view that leads to quite unnecessary use of multiple inheritance.
To my mind, inheritance is the basic tool for code reuse. It is an extension of the idea of a library of functions. Good programmers have always been alert to the possibility of writing simple functions as building blocks for more complicated ones. They store the simple ones in personal libraries so that they can reuse them. Inheritance provides the method fora more sophisticated approach and one where you never have to touch clean, fully-debugged code. Indeed, if you do go back to tweak your code simply for the benefit of some program there is a fundamental blind-spot in your perception of OOP. Any alteration to the public interface of a fully developed class is a serious breach of the unwritten agreement between your class and the code that uses it. If you want to make changes do it via inheritance; that is what it is for.
There seem to be two quite distinct ideas about this. On the one hand there are those who think that polymorphism is simply a version of overloading. I think this is an abuse of the word. To me, and many others, polymorphism relates to runtime selection of a function or of a function's behaviour. It is possible, via mechanisms such as switch statements, to provide polymorphism in languages such as C. C++ provides a special mechanism, virtual member functions, to support polymorphism. It needs the support of inheritance but is a separate and distinct C++ resource.
Unless your class structure needs polymorphism (i.e. must delay some decisions till runtime) do not clutter your classes with virtual functions. Not only is there some slight runtime cost in both code size and speed but you are setting a trap for yourself. You should be writing your code so that errors can be detected as early as possible, preferably by the compiler. I have even seen a suggestion that we should design programmer's editors that will detect syntax errors (at the back of my mind lurks the thought that I saw some such editor about eighteen months ago).
Appreciate C++ Mechanisms
Quite a number of mechanisms have been introduced in C++ to sidestep some of the more awkward methods of C. You may argue for some time about the relative advantages of using #define and const float to provide the value of pi in your work and I can't see any great advantage one way or other.
When it comes to providing macros I think that C++ template functions win hands down. They aren't totally problem free but they are very substantial improvement on C's use of #define. I think that programmers moving from C should always ask themselves if their use of #define is appropriate.
Both C and C++ make extensive use of pointers but most C pointers should be converted to references in C++. The strength of the C++ pointer is to be more economical with your use of stack space (always assuming that your compiler implements parameter passing and local variables via a stack). C programmers are parsimonious with using malloc and free, C++ programmers should be profligate with the C++ equivalents of new and delete. The mechanism for getting dynamic storage has been made easy to encourage you to use it.
Know What the Defaults Will Be
There are a number of places where C++ provides defaults in addition to those that already plague C programmers (by the way, do you know if your code relies on char being specifically signed or unsigned?).
If your class does not need a meaning for assignment inhibit the default. If your class contains a pointer write your own copy constructor. If you introduce a pointer into a class re-write its copy constructor.
Remember that inheritance is private by default (unless you are one of those oddballs who insist on using struct to declare active data-function combinations).
Remember that the onus for cleaning up is yours, not the operating system's. Just because your OS cleans up for you does not mean that you should rely on it. In fact I could argue strongly that it is not the job of an OS to tidy up and those that do so are pre-empting my right to pass data blocks around. Why shouldn't I get some dynamic memory and pass its address to another program? If the OS doesn't realise that I have passed access to another task and recovers the space when the initiating task ends then my programs will be in a mess.
Don't Use Cast to Silence the Compiler
Cast is powerful tool. I always think of it as a message to the compiler that goes something like this:
I know what I am doing so do it my way.
As long as the compiler can make sense of your syntax it will do it regardless of the consequences. You should never use a cast if you do not know exactly what it will cause to happen. Just because it silences the compiler does not mean that what you have written is correct, all it means is that the compiler can produce some code that it thinks will meet your instructions.
I wonder how many of the above points are ones that you dis-agree with? Or how many other points would you pass on to those converting to OOP in C++? Please send your points to Mike Toms.