C++ Review

We like C++, and use what some would consider advanced features.  Hopefully, this is mostly an issue of the internal structure, but in a few places, namely filling in class interfaces, you might be exposed to some of it.  Since we've supplied a significant amount of sample code (look in Behaviors/Demos) and template files of the most common classes (look in project/templates) this shouldn't be too problematic.

In any case, here's the breakdown of what's going on in the SampleBehavior code.

Header Files

 #ifndef INCLUDED_FILENAME_h_
#define INCLUDED_FILENAME_h_

// [...]

#endif

You will almost always see lines like these at the top and bottom of a header file (in both C and C++).  This prevents multiple redefinitions of datatypes and globals when the same header file is included multiple times.  The #define ensures that the first time it's included is also the only time (within that compilation unit).

Inheritance

 #include "Behaviors/BehaviorBase.h"

class SampleBehavior : public BehaviorBase {
// [...]
};

This declares a new type SampleBehavior, to inherit all the functions of BehaviorBase.  The public keyword tells the compiler to make all the inherited functions public.  (You can have private or protected inheritance too, so that only you can call the inherited functions.)

There is an #include to tell the compiler where to find the BehaviorBase class.  Otherwise it wouldn't know what you're inheriting from.

Constructors

 class SampleBehavior : public BehaviorBase {
public:
// [...]
};
A class's constructor is called automatically each time an instance of the class is created.  A constructor is defined by a function with the same name as the class.

A constructor is special in that it has no return type, and it can be followed by a `:' and a list of constructor calls to superclasses (classes being inherited from - i.e. BehaviorBase) and member variables.

In general, whenever you inherit from a class, you should make sure you call your superclass's constructor.

A destructor can also be specified, which would be called automatically when the class is destroyed.  (The destructor for this class would be called ~SampleBehavior(); .)

Overriding Functions

 class SampleBehavior : public BehaviorBase {
public:
SampleBehavior() : BehaviorBase() {}

virtual void DoStart() {BehaviorBase::DoStart();}
virtual void DoStop() {BehaviorBase::DoStop();}
virtual const char * getName() const {return "SampleBehavior";}
};

These three functions are defined in BehaviorBase.  By redefining them in a subclass, you can either replace or add to the default behavior.

Since BehaviorBase does some housekeeping for us in DoStart() and DoStop() (reference counting) we want to add on to it - so we should be sure to call its implementation as well.  BehaviorBase doesn't even define the getName() function, so we don't need to call it.

virtual Keyword

virtual functions are a little slower than regular function calls (only slightly!), but allow polymorphism. (A class which provides a single interface to doing something, with many different ways of doing it.  This is a central concept of good Object Oriented design.)

So, what's that virtual keyword mean?  It means that the subclass's implementation should be called instead of the superclass's.  For instance, to the framework all Behaviors are instances of BehaviorBase - it doesn't know about your specialized classes.

Without virtual, when the framework code wanted to start or stop a behavior, it wouldn't know which implementation of DoStart() or DoStop() to call.  If it wasn't for virtual functions, it wouldn't run your custom start/stop code, only the base class's code.

With the virtual keyword, the compiler inserts code into the function call to lookup the actual subtype of BehaviorBase that we're starting/stopping, and call that code instead of the base class.

Strictly speaking, virtual is only needed in the base class, and marks that function as virtual for all subclasses, whether they mark it as virtual or not.  But it's good style to remind potential subclassers that the functions are virtual.

C++'s virtual is the flip side of Java's final.  In Java, every function which is not declared final is by default "virtual". (Probably the "right" way of doing it, imho... oh well.)

const Keyword

You would think this would be simple... but there are four different places const can appear, and it means slightly different things.

  • Before a data type: const int x=3;
    • This tells the compiler to complain if you later change the value.  Good for a sanity check, but can also allow the compiler to make optimizations for more speed! :)
  • Before a pointer/reference: const int * p1;
    • This is a little backwards - you might think that means you can't change what it's pointing to, but that's not quite it.  You're free to reassign the pointer.  What it means is you can't change the data it's pointing to.  In other words, the following would be illegal: *p=7; but p=NULL; is fine.
  • "Inside" a pointer/reference: int const * p2;
    • This is the flip side of the above case - now you can change the data, but you can't change what it's pointing to.
    • You can combine the two types: const int const * p3;
  • After a member function: void f() const { cout << "hello world" << endl; }
    • This makes a promise to the compiler that f(), and every other function that f() calls, will leave the class it's a member of untouched.  So, in BehaviorBase, getName() cannot modify any values in the class.  This can be both a matter of sanity checking, and also optimization.

References (&)

These are close cousins to pointers - they allow a class to be passed to functions without making a copy of the class for each function call.  You use it the same way as the '*' denotes a pointer, but thereafter, instead of using '->' to access members, you can use '.'.  This can be a little cleaner than the messes pointers can lead to, and guarantees that the function won't receive a NULL pointer.

In the following example, event is a reference to the EventBase being passed. 

 void processEvent(const EventBase& event) { /* ... */ } 

Notice the use of const.  References are commonly const so that the original is not accidentally modified.

Unless the function receives a const reference (see above), this also means that any changes to the class will be taking place in the original class that was passed.

Templates

Templates are "clean" macros.  For instance, a function template:

 template <class T>
T min(T a, T b)
{
if(a < b) return a; else return b;
}

This allows anything which supports the < operator to be passed and have the minimum of the two returned.  Function calls are especially nice - often the compiler will automatically figure out what you want to use for T:

 string a="apple";
string b="orange";
cout << min(a,b) << endl;

This would output apple.  (aside - notice that min is receiving copies of a and b, which could potentially be large.  It wouldn't matter for ints, but since templates allow different types to be passed, such as say, strings,  it would be better to say:
T min(const T& a, const T& b).  This was left off for clarity of the example.)

However, you can also specify T explicitly:

 float a=3.4;
float b=2.2;
cout << min<int>(a,b) << endl;

This would cast a and b to int first, and then compare, outputting 2. If you left off the <int>, it would use the float version instead, and would then output 2.2.

Some templates specify a constant to be used (for instance, a number of array elements to allocate) which allows compile time allocation and optimization, and others specify a type (as in the previous examples).

Similarly, classes can also be templated, which allows data members of variable type, as well as templated member functions.

developer resources

Last modified: 2010-01-10