In Objects & Classes we started to develop a Track class. Suppose we want to be able to create track objects specifying mass and energy, then the first thing to be done is to add the constructor to the class definition file (i.e. the header file):-
class Track { Float_t fEnergy; Float_t fMass; Float_t fMomentum; Track(Float_t mass, Float_t energy); Float_t GetEnergy(); void SetEnergy( Float_t energy); };Note the function is called Track. The other odd thing is there is no return value, the user cannot directly call the constructor function. Having declared it, we can now add its definition to the implementation file:-
#include "Track.h" Float_t Track::GetEnergy() { return fEnergy; } void Track::SetEnergy( Float_t energy ) { fEnergy = energy; fMomentum = sqrt( fEnergy*fEnergy - fMass*fMass ); } Track::Track(Float_t mass, Float_t energy) { fMass = mass; SetEnergy(energy); }Remember the Track:: stuck at the front of each function is just telling the compiler that this belongs to (or is in the scope of) Track. The constructor function starts with:-
fMass = mass;so sets the track's mass. The next line deserves a bit more attention:-
SetEnergy(energy);SetEnergy is another of the particle's member functions. So in effect the object is sending itself a message. People talking to themselves is generally regarded as a bad thing, but with objects is often a sign of good class design. In this simple case, having already solved the problem of keeping momentum, mass and energy self consistent, we reuse the solution. Its another part of the OO ethic: Only solve a problem once. As the above example shows, when making internal calls like this, the member functions just looks like a normal function. Only if one object wants to call one of its siblings does it have to designate the target object, thus requiring the full object.function syntax.
Track MyTrack; Track *MyTrackPtr = new Track;now becomes:-
Track MyTrack(.135, 1.0); Track *MyTrackPtr = new Track(.135, 1.0);Of course we cannot send a object its constructor message, so the argument list is either supplied behind the identifier or the class name. As has been mentioned before,
Int MyInt = 3;we can write:-
Int MyInt(3);so we can treat int as having a constructor that takes a single argument that is an int. Although this notation isn't normally used like that it is often used in constructors. We could write:-
Track::Track(Float_t mass, Float_t energy) { fMass = mass; SetEnergy(energy); }as:-
Track::Track(Float_t mass, Float_t energy): fMass(mass) { SetEnergy(energy); }the assignment has become an initialisation. The colon after the argument list signals this construction. If more than one variable is initialised like this they must be separated by commas. Although the syntax is not exactly intuitive, it does allow the compiler to optimise the code.
Track::Track(): fMass(0.), fMomentum(0.), fEnergy(0.) {}The empty argument list shows this to be the default constructor. All the data members are initialised to zero leaving nothing for the function to do. Odd though this is, it is not an uncommon to see constructors like this.
Track MyTrack;are deleted automatically, once control leaves the innermost compound statement ( i.e. the innermost {..}) that contains the statement. For objects created dynamically:-
Track *MyTrackPtr = new Track;the object remains until explicitly deleted, although, in the above statement, the pointer MyTrack is a local and will get deleted automatically. To delete a single object the delete operator is applied to a pointer to it:-
delete MyTrack;To delete an array:-
delete [] MyTracks;note that the array [] really is empty - the compiler takes care to record the array size.
Whenever an object is deleted its destructor member function is called. Its understandable why constructors are so important, objects must be properly initialised before they can be used, but is it really necessary to have a special member function that gets called when the object is about to disappear? In many cases, including our Track class as developed so far, the answer is no, we could leave the compiler to invent a default no-op one. However suppose our Track object contained a list of detector hits from which it was built. Without going into detail, its likely that this would be some kind of dynamic object owned by the Tack object and accessed via a pointer. Now when it comes time to delete the Track object, we want this list to be deleted, but probably not the hits that it points to! The compiler cannot possibly know, when it comes across a pointer in an object whether it points to something owned by the object and to be deleted as well, or simply something related to, but independent of, the object. So the rule is If an object, during its lifetime, creates other dynamic objects, it must have a destructor that deletes them afterwards. Failure to tidy up like this can leave to orphan objects that just clog up the memory, something that is called a memory leak. Even when a default is acceptable, its a good idea to define a destructor, so we should add the following line to our header:-
Track::~Track() {};As with the constructor, the function does not return a value. The function is called the class name but with a leading ~, which is the C++ complement operator - a bleak reminder that this function is the complement of creation! Currently it does nothing, as shown by the empty {}.
Track MyTrack;then this object is brought into existence at the start of the program, even before the main function starts to execute. It persists until the main function exits. On the other hand, were we to write:-
{ Track MyTrack;}within a function, then the {..} would ensure that the object would be created and destroyed all within this single line. This means that objects can take every possible role in a program play, from a walk-on part, to a lead character. As examples of these two extremes:-