Instead of providing a toy example, I figured I will just annotate an existing open-source C++ class. I will talk about a class in the Synthesis Toolkit(STK). I don’t talk about actually implementing classes much, so that’s for next time. Also, I haven’t compared ObjC code to C++ code since the Part I, so I will probably do that next time.
Let us take a look at the existing Instrument class in the Synthesis Toolkit, which is a set of libraries written in C++ for audio signal processing and computer music. Here’s the link to the file I will be talking about –> source file link.
Taking it from the top. Remember that Flute.h is analogous to the @interface file in ObjC.
00025 #ifndef STK_FLUTE_H
00026 #define STK_FLUTE_H
00028 #include "Instrmnt.h"
00029 #include "JetTable.h"
00030 #include "DelayL.h"
00031 #include "OnePole.h"
00032 #include "PoleZero.h"
00033 #include "Noise.h"
00034 #include "ADSR.h"
00035 #include "SineWave.h"
This illustrates a classic C++ idiom. One of the few places that preprocessor directives are essential is to avoid multiple header file inclusion. The directives above say that if the symbol STK_FLUTE_H is not defined , define it and include all those header files only if STK_FLUTE_H is not defined. #endif closes the #ifndef STK_FLUTE_H block. In Objective-C(with Apple’s developer tools), one always uses #import “headerfile.h” which automatically safeguards against multiple inclusion of header files.
00037 class Flute : public Instrmnt
00081 StkFloat computeSample( void );
00083 DelayL jetDelay_;
00084 DelayL boreDelay_;
00085 JetTable jetTable_;
00086 OnePole filter_;
00087 PoleZero dcBlock_;
The protected keyword indicates that the following functions and variables will only be public to this class and the classes that inherit from this class.
This is a destructor: You put all your dynamic memory deallocation here, but never call this function from your own code as it gets called automatically. Remember auto_ptr<T>? I wouldn’t be surprised if the way it worked is: The memory to which the raw pointer points to is deallocated when the smart pointer’s destructor is called automatically—remember the raw pointer is “wrapped” by the smart pointer.
It turns out for the particular Flute class there is no dynamic memory allocation, so the destructor is empty(I looked at the implementation file).
In case the StkFloat and DelayL above were confusing you, they are nothing but user-defined types(either user-defined classes or typedef aliases–more on typedef later).
There is more to know about classes, but let us discuss the important topic of inheritance. Remember the “IS-A” relationship. So, a flute “is-a”(n) instrument. This might seem obvious and unnecessary, but here we are looking at models of physical instruments. Thinking a little about “IS-A” and “HAS-A”(“HAS-A” is used for composition, which is essential in the ObjC-based Cocoa framework, possibly more on this later) will go a long way in helping us design classes when we are modeling more abstract entities.
Let us briefly take a look at the “parent” class of Flute, Instrument(if you download the source code for the STK framework, you can find Instrument.h and Instrument.cpp(Instrument.cpp is analogous to @implementation). I will talk about Instrument.h available here.
class Instrmnt : public Stk
00024 virtual ~Instrmnt();
00027 virtual void noteOn(StkFloat frequency, StkFloat amplitude) = 0;
What is this virtual business? Here’s a concise description:
Within the C++ the virtual keyword identifies to the compiler that a class function may be overridden in a derived class. That is, the derived class may supply a function of the same name and argument list, but with different functionality defined in the function body.
from this link. Now, note that
virtual void setFrequency(StkFloat frequency);
has a function body in Instrmnt.cpp while
virtual void noteOn(StkFloat frequency,...) doesn’t. If a function body existed, it would have been found in Instrmnt.cpp. This lack of a function body is indicated by the = 0 at the end in Instrmnt.h . Why would someone not have a function body and actually declare the function? Answer: To capture the commonality of all the classes that will be derived later from the base class, so that the user might do something like this:
//Warning: untested code just used for illustration purposes
Flute flute(440.0); //some made up lowest frequency 440.0
vector < Instrmnt > instrumentlist;
vector< Instrmnt > instrumentlist::iterator pos;
for(pos = instrumentlist.begin(); pos != instrumentlist.end; ++pos)
pos->noteOn(440.0,200.0); //contrived values
post->noteOff(50.0); //contrived value 50.0
Also observe that it doesn’t make sense for a generic instrument to have a noteOn function, but it does make sense for the derived classes like Flute. Note that I’m treating both flute and clarinet as instruments in the above code, which enables me to write better code, instead of handling each type of instrument in a switch statement of if-else if-else construct.
III. Virtual Destructors:
Finally, let us talk about virtual destructors. Here’s a rule of thumb from Scott Kleper and Nicholas Solter(authors of Professional C++):
Always make your destructors virtual
I would qualify that with make your destructors virtual when necessary(obviously the STK authors don’t make destructors virtual when not necessary). Suppose for a moment that Flute and Instrmnt had dynamically allocated memory, so that both the Instrmnt and Flute destructors had important deallocation code, and I had the following situation:
Instrmnt* instr = new Flute(440.0);
This would call Instrmnt’s destructor and not Flute’s destructor(since I didn’t make Flute’s destructor virtual). If I had, I wouldn’t have the problem.
This is just the tip of the iceberg when it comes to talking about C++ classes. You will find more information in Bruce Eckel’s Thinking in C++ Volume I, or Professional C++ by Nick Solter and Scott Kleper