Programming in C++
From WiiBrew
This is an article I made for a programming team of mine but posted here as well. Supplied by WiiPhlex
C++ is a feature-rich language, and every C++ feature is useful in certain situations. However, the code for this articledoes not venture into all of those situations. It does hit some of them, though, so I’m going to devote a little time to each of these features now. I’ll progress from what I consider the easier stuff to the more complex features.
Contents |
Inline Functions
Let’s start with inline functions.
Pure Virtuals
At this point our code is starting to become much more object-oriented, but there’s still something that should be bothering you—not all animals breathe air. For example, if we added a CEel class, we’d have to make sure to add a Breathe function for it, otherwise, we’d incorrectly be calling BreatheAir for eels!
Right now we’re saying to the compiler “unless I tell you otherwise, assume that all animals breathe air.” It might be better if we could say “don’t make any assumptions; I will provide a Breathe function for all derived classes.” That way, we could be sure that each animal is breathing correctly.
This is where the notion of a “pure virtual function” comes into play. A pure virtual function (or pure virtual, to its friends), is a function that only exists for derived classes. It has no base class implementation.
You declare one by putting “ = 0” after the function declaration in your class.
Here’s how we’d use one in our code:
class CHeart { /* whatever */ }; class CAnimal { protected: virtual void Breathe(void) = 0; CHeart m_Heart; }; class CFish : public CAnimal { protected: void Breate(void) { BreatheWater(); } }; class CCat : public CAnimal { protected: void Breate(void) { BreatheAir(); } }; class CDog : public CAnimal { protected: void Breate(void) { BreatheAir(); } };
Essentially, the only thing we’ve done here is put an “= 0” in place of
CAnimal::Breathe. That tells the compiler that our Breathe function doesn’t exist
in our base class, but must exist in every class that derives from our base (we
get compile errors if it doesn’t).
One important caveat to creating pure virtual functions: Any class with a pure virtual cannot be instantiated. That is, with the pure virtual inside CAnimal, you can’t ever create a variable of type CAnimal. This makes sense, because what would the compiler do if you created a variable of type CAnimal, and then called Breathe on it? It would have no idea what kind of animal it would be dealing with, so it wouldn’t know what Breathe function to call.
You can still create references and pointers to that base class, however, and in reality, that’s where the power of polymorphism really shines, because it allows you to do things like this:
CAnimal *CreateRandomAnimal(void) { CAnimal *theAnimal = NULL; switch(rand() % 3) { case 0: theAnimal = new CCat(); break; case 1: theAnimal = new CDog(); break; case 2: theAnimal = new CFish(); break; } return(theAnimal); } void Foo(void) { CAnimal *pAnimal = CreateRandomAnimal(); pAnimal->Breathe(); // automatically calls the correct function! delete pAnimal; }
In the code example above, we’ve got a function that creates a random type of animal. It returns that random type of animal in a pointer to its base class, CAnimal. (Remember, you can’t create CAnimals, but you can create pointers to CAnimals). When Foo tells the new CAnimal pointer to breathe, the compiler automatically knows which function to call.
Think about how cool that is for a moment. Foo doesn’t know or even care what type of animal it’s dealing with—it just says Breathe and the compiler does the rest. In fact, we could add 500 different types of animals, and rewrite CreateRandomAnimal so that it randomly picked one of those 500 animals, and so long as all 500 animals derived from CAnimal and all 500 implemented a Breathe function, Foo would work without us changing one line of its code. That’s the power of polymorphism!
Virtual Destructors
I want to cover one last, very important topic on polymorphism before moving on. We need to talk about what happens when base and derived classes are destroyed. Each C++ class has a constructor and destructor, which tell the compiler what to do when that object is created or destroyed. Normally, if the constructor allocates any memory for the class, the destructor frees that memory. For example, let’s say we wanted to create a couple of animal parts dynamically:
class CHeart { /* whatever */ }; class CGills { /* whatever (for the fish) */ }; class CAnimal { public: CAnimal() { m_pHeart = new CHeart; } ~CAnimal() { delete m_pHeart; } protected: virtual void Breathe(void) = 0; CHeart *m_pHeart; }; class CFish : public CAnimal { public: CFish() { m_pGills = new CGills; } ~CFish() { delete m_pGills; } protected: void Breate(void) { BreatheWater(); } CGills *m_pGills; }; class CCat : public CAnimal { protected: void Breate(void) { BreatheAir(); } }; class CDog : public CAnimal { protected: void Breate(void) { BreatheAir(); } };
Here you can see I’ve added a couple of things. First, I changed CAnimal so that it dynamically allocates a heart when it constructs, and deletes that heart when it’s destroyed. I’ve also added a similar mechanism to Cfish—the fish object now creates some gills when it’s constructed, and destroys those gills when it’s destroyed. Unfortunately, there’s a bug in that code, and it’s a sneaky one. Let’s say I have the same Foo function:
void Foo(void) { CAnimal *pAnimal = CreateRandomAnimal(); pAnimal->Breathe(); // automatically calls the correct function! delete pAnimal; }
The problem here is that Foo only knows it’s dealing with a CAnimal. So, when it says delete pAnimal, the CAnimal destructor is called, but not the destructor for any derived objects. So, if CreateRandomAnimal happens to create a fish, we’ll create a heart and some gills, but when we call delete, we will end up calling only the CAnimal destructor, and wind up deleting the heart but not the gills. This is bad because we leak memory, to say nothing of the spookiness in having some disembodied gills floating around somewhere.
To fix this problem, we need to make the CAnimal destructor virtual. When we add the virtual keyword to the beginning of the destructor line, we solve our problem. The virtual keyword has a slightly different meaning when applied to destructors. Ordinarily, virtual means “Hey, Mr. Compiler, check the derived classes for this function, and if you find it down there, don’t call this one, call the derived one instead.” But, when applied to the destructor, the virtual keyword says “Hey, Mr. Compiler, you need to call the destructor for the derived classes, as well as the destructor for this object.” C++ does things this way because if both the base class and the derived class allocate memory, they’ll both need to be called.
Virtual Functions
Now that we’ve got hearts for our fish, dog, and cat class, we might decide to give them a little more life. We know that all animals breathe, so we can easily add a Breathe function to our code:
class CHeart { /* whatever */ }; class CAnimal { protected: void Breathe(void); CHeart m_Heart; }; class CFish : public CAnimal { /* whatever */ }; class CCat : public CAnimal { /* whatever */ }; class CDog : public CAnimal { /* whatever */ };
This works, but the problem is that some animals breathe differently than others. Fish breathe water; cats and dogs prefer air. Having one Breathe function for all three animal types would force us to perform a switch inside the function, and do different things depending on what kind of animal we are:
void CAnimal::Breathe(void) { if (ThisAnimalIsADog() || ThisAnimalIsACat()) { BreatheAir(); } if (ThisAnimalIsAFish()) { BreatheWater(); } }
There are several painful points in the previous code. For starters, how do we implement the ThisAnimalIsADog, ThisAnimalIsACat, and ThisAnimalIsAFish functions? Additionally, if we add new animals, we have to go back to this function and add if statements. Even worse, if none of the if statements are true, the animal doesn’t breathe at all!
Polymorphism was designed to solve just this type of problem. C++ contains a feature called “virtual functions.” Virtual functions are functions, which are defined for both the base and derived classes. When you call a virtual function of a class, the compiler looks at the type of class, and automatically calls the correct function.
Let’s see how it looks:
class CHeart { /* whatever */ }; class CAnimal { protected: virtual void Breathe(void) { BreatheAir(); } CHeart m_Heart; }; class CFish : public CAnimal { protected: void Breate(void) { BreatheWater(); } }; class CCat : public CAnimal { /* whatever */ }; class CDog : public CAnimal { /* whatever */ };
Here we’ve introduced a couple of things. First, we’ve put the virtual keyword before the Breathe prototype in CAnimal. This tells the compiler that Breathe is a virtual function. Second, we’ve added a Breathe function to CFish.
Now let’s say we have some code as follows:
void Foo(void) { CFish fish; CDog dog; dog.Breathe(); // calls CAnimal’s Breathe fish.Breathe(); // calls CFish’s Breathe }
That’s polymorphism! When we say dog.Breathe, the compiler knows that dog is of type CDog. Since CDog doesn’t define a Breathe function, we end up inside CAnimal’s Breathe function. Conversely, when we call fish.Breathe, the compiler notices that we’ve created a Breathe function just for CFish, and calls that instead of CAnimal’s breathe.
Catching Different Types and Catching Everything
You’re not limited to just catching one type of exception. Here’s an example that catches both strings and integers:
void Foo(void) { if (rand() % 2) throw(“Error in function foo!”); else throw(5); } void main(void) { try { Foo(); } catch(const char *strError) { printf(“Caught string: %s”, strError); } catch(int iError) { printf(“Caught integer: %d”, iError); } }
I’ve modified Foo so that it randomly throws a string or an integer. To accommodate this, I’ve also added a new catch handler that catches integers instead of strings. You can also add a special catch statement, which many programmers call a catch all, that will catch anything for which you haven’t specifically written a catch handler. You create a catch all by putting an ellipsis (three dots) inside the parentheses of the catch statement, like so:
void Foo(void) { switch(rand() % 3) { case 0: throw(“Error in function foo!”); case 1: throw(5); case 2: throw(5.08f); } } void main(void) { try { Foo(); } catch(const char *e) { printf(“Caught string: %s”, e); } catch(int e) { printf(“Caught integer: %d”, e); } catch(...) { printf(“I caught something, but I have no idea what it is.”); } }
In this example, Foo now throws strings, ints, or floats. The string and int throws end up in the string and int handler, but since we haven’t defined a handler for floats, the float throw ends up in the catch-all handler.
Nested Try Blocks and Re-Throwing Exceptions
Yep, you can nest try blocks just like you can nest any other block of code. Here’s an example:
void main(void) { try { Foo(); try { throw(“Another Error Occurred”); } catch(const char *e) { printf(“An error occurred after Foo: %s”, e); } } catch(const char *e) { printf(“Caught string: %s”, e); } catch(int e) { printf(“Caught integer: %d”, e); } catch(...) { printf(“I caught something, but I have no idea what it is.”); } }
In this example, the very first catch handler catches the “Another Error Occurred” error. You can nest try/catch blocks as deep as you’d like.
You can also re-throw an exception, if you catch something and you have no idea what to do with it. Here’s an example of that:
void main(void) { try { Foo(); try { throw(“Another Error Occurred”); } catch(const char *e) { // our error is caught once here printf(“An error occurred, and I have no idea what to do” “ about it, so I’m re-throwing.”); throw; } } catch(const char *e) { // our error is caught again here printf(“Caught string: %s”, e); } catch(int e) { printf(“Caught integer: %d”, e); } catch(...) { printf(“I caught something, but I have no idea what it is.”); } }
In that example, I changed the innermost catch handler, and made it rethrow the error. The error is caught by the innermost handler, which prints a message saying it doesn’t know what to do with it, and then re-throws. The next handler up then catches the error. This is extremely nifty, because it allows you to log errors and then hand them off without actually doing anything about them.
The Do’s and Don’ts of Using Exceptions
As you now know, exceptions can be a powerful tool. However, just like any tool, exceptions are not appropriate in all situations. One of the most common errors I see beginning C++ programmers make is to sprinkle exceptions everywhere in their code. They throw at the slightest hint of something gone wrong, and often don’t write catch handlers as close to the error as possible. Ironically, this leads to code that’s very difficult to follow and debug.
So, here are a few bullet points on the do’s and don’ts of using exceptions:
- Don’t use exceptions as a replacement for errors. An error is an alternate flow of logic; an exception is truly something out of the ordinary. For example, if you prompt users for a password to begin play at a certain level of your game, and they enter the incorrect password, that’s an error. Your program takes an alternate path of execution, probably displaying an error like “hey, your password’s wrong!” and allowing them to enter it again. However, if you run out of memory while trying to validate their password, that’s an exception, because it’s something you wouldn’t normally expect to happen.
- Do use exceptions to separate error logic from core logic. Isolate your errorhandling code in a catch block (or many catch blocks), so that you end up with the exceptional cases separated from the normal cases.
- Don’t use exceptions where a normal switch statement or other program feature would suffice. Again, you should use exceptions only to handle truly bizarre events in your code. The normal flow of logic through your program should be exception-free.
- Don’t just drop a library that uses exceptions into a program that doesn’t. If you must, surround the library interface with a set of wrapper classes that catch the exceptions and convert them into whatever non-exception error handling system you have (return values, global variables, whatever).
- Don’t convert existing, already robust code to use exceptions. Also, as a corollary to the last item—it’s very tedious and time consuming to take a robust program that doesn’t use exceptions, and change it so it does. If your program’s working correctly and robustly already, don’t change it.
- Do use a class hierarchy for exceptions. By this, I mean, create a base class called CException or something, and derive from it different exception classes —COutOfMemoryException, CArrayOutOfBoundsException, etc. Use RTTI (discussed later) to figure out what you’ve caught, or, create a virtual function that returns the class name of the exception as a string.
- Don’t worry about using exceptions in small programs. If you’re writing a one-page command-line utility program, exceptions are probably more trouble than what they’re worth. Just because you have a great tool for making code robust doesn’t mean that all of your code needs to be robust— oftentimes it’s better to concentrate on the game itself at the expense of the internal tools. Just make sure that what you’re releasing to your end users, the players, is robust!
Exception Handling Wrap-Up
Believe it or not, there are many exception-handling topics that I didn’t cover here. Before you go charging off and using exceptions in your next 3-D engine, you should spend more time learning about the fine points of their usage. For example: What happens if you throw an exception inside a constructor? What happens if you’re creating an array of objects, and midway through the construction of the objects, you throw? How many array objects are valid? Can you write a handler for exceptions that no one catches?
Don’t start using exceptions heavily without first being able to answer those questions. Exceptions are new, unlike anything we’ve seen in C, and we need to move carefully to ensure we’re using them correctly.
C++ Style Casting
You’re probably intimately familiar with casting—you know, saying to the compiler, “Yes, I know it’s an integer, but pretend it’s a char, OK?” You’ve probably seen code like this:
int i = 5; char c = (char)(i);
Essentially, what you’re doing here is “casting” the value (i) to a char. It’s supposed to be an int, but by doing a cast you’re saying “stuff this into a char and don’t complain!”
Also, you’ve probably seen pointer casts, like what you have to do when you use malloc:
char *str = (char *)malloc(50);
You need the (char *) cast in there because malloc returns a void *, and the compiler doesn’t want to automatically convert void *’s into char *’s.
So, that’s how you do it in C, and C++ still allows you to do this form of casting. But, there are several ugly things about this form of casting:
- It isn’t something for which you can easily search. Say you’ve got a chunk of code, and you want to know every place where that code is casting. How do you do that? You can’t just search for parentheses, because they’re used everywhere. If your search tool doesn’t support wildcards in the search statements, you’re basically out of luck, and even if it does, it’ll errantly find prototypes of functions that take one argument: A cast like (char *) looks the same as part of a C function prototype, void foo(char *);
- It doesn’t differentiate between dangerous casts and innocent casts. Say you’ve got a pointer to class D, which derives from class B. Casting your pointer to (B *) is safe, because we know B is a base class of D. However, casting your (D *) pointer to, say, (char *), isn’t nearly as safe—in that situation, you’re playing directly with bytes of memory, and that’s risky. In C, you accomplish both safe and risky casts the same way.
C++ style casting was designed to solve these two main problems. C++ style casts are obvious, and they differentiate between safe and unsafe casts. There are four new cast keywords in C++.
reinterpret_cast
Use this cast to do anything.You can use it to convert void pointers to floats, convert chars into pointers, and do all sorts of other risky stuff.
static_cast
Use this cast to do “safe” casting, for example, to convert a derived class pointer into a base class pointer (called “upcasting”).You can also use it to perform implicit type conversions (stuff the compiler would have done anyway, like converting an int to a float if you’re adding it to another float).You can also use static_cast to convert a void pointer into something more useful.
The compiler won’t let you use a static_cast for anything that’s really dangerous.To program the safest possible cast, always use static_cast; don’t use reinterpret_cast until the compiler says that you must.
dynamic_cast
Use this to perform dynamic downcasts (converting from a base class pointer into a derived class pointer). In other words, you can use dynamic_cast to ask “Hey Mr. Compiler, does this base pointer really point to a derived object?”
If it does, the compiler will give you back a pointer to that derived object. If it doesn’t, the compiler will give you a NULL pointer back. This cast is a part of doing run-time type identification, which we’ll talk about a little later.
const_cast
Use this to add or remove “const-ness” from something. For example, if you have a const class and need to convert it to nonconst, you can use this.You can also use it to temporarily make something const. You can also use const_cast to add or remove volatility from something. If you’ve got a volatile object, and you need it not to be, use const_cast. Or, if you need something volatile, const_cast is the tool for the job.[/quote]
To use the casts, you first type the keyword for the cast you want to use. Put the thing you want to cast to inside < and > symbols immediately after the cast keyword. Then, put the stuff you want to cast from inside parentheses immediately after the >. Here’s an example:
int i = 50; char *pMemory = reinterpret_cast<char *>(&i); That code is functionally equivalent to the old-school: int i = 50; char *pMemory = (char *)(&i);
As you can see, using C++ casts, it’s possible to easily search for all “dangerous” casts. Just search for reinterpret_cast, a C++ keyword. Here’s a short example showing the new C++ casts in the wild:
class CMyClass : public CMyBaseClass { /* whatever */ }; void foo(const CMyClass *myClass, CMyBaseClass *myBaseClass) { // use static cast to convert myclass into my base class CMyBaseClass *pStaticCastedClass = static_cast<CMyBaseClass *>(myClass); // use dynamic cast to see if a base class pointer really // points to a derived class CMyClass *pDynamicCastedClass = dynamic_cast<CMyClass *>(myBaseClass); if (pDynamicCastedClass) { printf(“hey, myBaseClass really does point to a CMyClass.”); } else { printf(“nope, myBaseClass doesn’t really point to a CMyClass.”); } // use const cast to add or remove constness CMyClass *pNotConstClass = const_cast<CMyClass *>(myClass); // use reinterpret cast to do dangerous stuff char *pClassMemory = reinterpret_cast<char *>(myClass); }
Run-Time Type Identification (RTTI)
Now that you’ve gotten a grip on C++ casting, we can talk about another C++ feature— run-time type identification, or RTTI.
As its name implies, RTTI is a C++ feature that allows you to determine what type an object is. We saw a little of this in the previous section, where we learned that we can use dynamic_cast to effectively ask the compiler if something is of a given type. Technically, dynamic_cast is part of RTTI.
RTTI isn’t just about dynamic_cast, however. There’s another equally important keyword— typeid. The typeid keyword works very similar to how sizeof works—it’s not really a function, but you use it as if it were. The sizeof “function” returns the size of whatever you gave it, in bytes. The typeid “function” gives you an object called type_info that contains type information about what you gave it.
CAUTION
The compiler throws an exception if you pass NULL into typeid. The exception is of type bad_typeid. So, make sure you’ve got a bad_typeid catch block anywhere you could potentially be passing NULL into typeid.
Specifically, here’s what type_info contains:
class type_info { public: virtual ~type_info(); int operator==(const type_info& rhs) const; int operator!=(const type_info& rhs) const; int before(const type_info& rhs) const; const char* name() const; const char* raw_name() const; private: /* you can’t see this! */ };
As you can see, this type_info class consists of three functions and two overloaded operators. Let’s look at each in detail.
type_info::before
The before method isn’t terribly useful. You use it to determine if one type ID should be sorted before another. For example, if you have classes X and Y, you could use the before function to determine if X should come before Y:
if (typeid(X).before(typeid(Y))) { /* X comes before Y! */ } else { /* Y comes before X! */ }
Like I said, not the most useful thing in the world, but it’s handy in certain situations.
type_info::name
This method is much more useful. It returns the name of the thing you pass it:
class MyClass { /* yada */ }; void main(void) { MyClass x; printf(“x is a %s.”, typeid(x).name()); }
That code will print “x is a MyClass.” I’m sure you can find myriad uses for this: For example, you can save the name of the class that generates your saved game files inside the save file itself, so that you’ll always know what class to use to load it.
type_info::raw_name
This method is very similar to the name method, with one key difference—raw_name returns the decorated name, which isn’t human readable. The decorated name has at signs—@—and weird letters all over it, because it’s the name the compiler refers to the type as internally. Nonetheless, this name may be useful in situations where you need to compare things.
Overloaded Operators of type_info
The type_info class contains overloads for the == and != operators. These overloads allow you to compare the types of two objects directly, without having to do string comparisons on their names (or raw names). For example:
class MyClass { /* yada */ }; class MyOtherClass { /* yada */ }; void main(void) { MyClass x; MyOtherClass y; if (typeid(y) == typeid(MyOtherClass)) { printf(“So far, so good...”); } if (typeid(x) != typeid(MyClass)) { printf(“Something has gone horribly, horribly wrong.”); } if (typeid(MyClass) == typeid(MyOtherClass)) { printf(“Something is still horribly, horribly wrong.”); } printf(“x is a %s.”, typeid(x).name()); }
Here you can see that we’re doing some comparisons to see whether certain variables are of certain types.
Tip
Keep in mind that dynamic_cast can tell us the same thing however, dynamic_cast can be slightly slower than typeid, so only use dynamic_cast when you want a pointer to the object, and use typeid when you want to see if an object is equal to something.
RTTI Wrap-Up
Pretend this sentence contains the standard disclaimer about how you really should not use RTTI in your own projects until you know more about it. Here’s some other questions you can research on your own:
- What does typeid give you if you pass it a template class?
- What does typeid give you if you pass it a void * that actually points to a base class?
- Does typeid(5) == typeid(int)? Does typeid(5) == typeid(float)?
Templates
C++ supports a powerful feature, called templates, which can really help you out in certain situations. So, here’s how they work.
Template Functions
Let’s say you’re a C programmer who’s just spent the last two months coming up with the perfect function to swap two integers:
void swap(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; }
Now, let’s say you’ve just recently learned about C++ references, so you’ve rewritten your swap function to be even more glorious:
void swap(int &a, int &b) { int temp; temp = a; a = b; b = temp; }
Pretty nifty. This code is easy to read, and is reasonably optimized. You’ve got yourself a great tool for swapping integers.
But what about swapping floats? Arrgh! Now you have to create another function, to handle floats. You know that C++ will let you overload the function name so long as the parameters are different, so you write an overload for swap that takes floats:
void swap(float &a, float &b) { float temp; temp = a; a = b; b = temp; }
At this point you should be concerned, because you’ve used copy/paste inside your IDE. Any time you copy and paste code, you should get worried, because you’re creating two identical functions. If you find a bug in the int version of swap, you’ll have to go and make the exact same patch to the float version, and that wastes time (to say nothing of the headaches that come if you forget to update the other function—if you’ve done a lot of copy paste, you can also waste a lot of time chasing your tail, solving the same bug over and over again for different data types).
This is what C++’s template feature was designed to solve. Templates allow you to write code that operates on things of any data type—ints, floats, classes, structs, whatever. You simply tell the compiler what string you’d like to substitute for the variable type, write your algorithm, and the compiler takes care of “putting in” the correct types. In other words, you create the “template” for the code, and the compiler uses this template, along with a certain data type, to create the actual code.
It’s easier to see in code than explain in words, so here’s a version of swap that will work for all objects:
template<class T> void swap(T &a, T &b) { T temp; temp = a; a = b; b = temp; }
We start out with the keyword template, which tells the compiler we’re about to define a template function. Next, we put class T inside a tag, which tells the compiler, “Any time you see a T, you should replace it with whatever type is needed.” Next, we write our function, using T in place of int or float. T is effectively a placeholder for a variable type.
Now, when we call this function, the compiler automatically plugs in the correct types. For example, say we call the function like this:
int x=5, y=10; swap(x, y);
The compiler will automatically instantiate (make) a version of swap that works for ints. If we say:
double x=5.0, y=10.0; swap(x, y);
Then it’ll make a version that works for doubles. Pretty cool, isn’t it? We can even have it generate a version for a class:
CMyClass x, y; swap(x,y);
That will work, provided we’ve overloaded the = operator for CMyClass. So, that’s what you can do. Here’s what doesn’t work:
CMyClass x; int y; swap(x,y);
In this situation, we’re trying to use two different data types. As it’s written now, the swap template won’t accept two different types. However, we could rewrite it so it does:
template<class T1, class T2> void swap(T1 &a, T2 &b) { T1 temp; temp = a; a = b; b = temp; }
It gets a little trickier to make that version of swap work—you’d need to have operator = overloads, or you’d have to rely on implicit conversions (i.e., the compiler automatically knowing how to set an int equal to a float). I wrote that example mainly to show that you could specify as many different template parameters (type substitutions) as you want.
TIP
The compiler only generates the code that you need. It doesn’t automatically generate every possible version of swap or other template functions. When you add code that uses a float version of swap, it creates the float version.
Because of this, it’s possible that a template function may compile and link without any errors at first, but if you go back and use it for a different type, you may get errors. For example, if we didn’t have an operator = overload for a class we were using swap with, we’d get an error inside swap complaining about us trying to set one class equal to another. The Visual C++ compiler (and most other compilers) will also give you the line of code that caused the compiler to generate the errant template function, but it still sometimes takes a bit of thinking to deduce exactly what broke and why.
Template Classes
You’ve now learned how to create template functions. C++ also lets you create template classes. Here’s what that looks like:
template <class T> class MyTemplateClass { public: /* use T for the types of some variables here */ T m_MyData; };
You declare a template class basically the same way you declare a template function: You type the keyword template followed by the different template arguments. You then declare the template class just as you would any other class.
To instantiate a template class, you must give the compiler all the argument types the template requires. You do this as follows:
MyTemplateClass<int> m_IntClass; m_IntClass.m_MyData = 5; // m_MyData is of type int MyTemplateClass<float> m_FloatClass; m_FloatClass.m_MyData = 5.0f; // m_MyData is of type float
In that example, we’re instantiating a version of MyTemplateClass that uses ints. See how the template argument just hangs off the class name?
If our class template used more than one type of class, we’d need to separate the data types by commas:
template <class T1, class T2> class MyDualTemplateClass { public: /* use T for the types of some variables here */ T1 m_MyData; T2 m_MyData2; }; void Foo(void) { MyTemplateClass<int, float> m_MyClass; m_MyClass.m_MyData1 = 5; // m_MyData1 is of type int m_MyClass.m_MyData1 = 5.0f; // m_MyData2 is of type float }
You can also really warp your head, because you can put template classes in other template classes:
template <class T> class MyTemplateClass { public: /* use T for the types of some variables here */ T m_MyData; }; void Foo(void) { // create a template class that uses a template class that uses // ints MyTemplateClass< MyTemplateClass<int> > m_MyClass; // m_MyClass.m_MyData is of type MyTemplateClass, so it has another // m_MyData of type int. m_MyClass.m_MyData.m_MyData = 5; }
You see this more often than you might think. The STL library includes several template classes that use other template classes as template arguments. It can get downright weird trying to think about some of these, so the best advice I can give you is to simply practice!
C++ Wrap-Up
So, there it is—a quick tour through the C++ features that are used in this book. I hope this section has given you a deeper understanding of what C++ is all about— giving the programmers more tools they can use to make their job easier. As I said before, not all of the C++ features are useful in every situation. An experienced C++ programmer knows which tools fit the job, and uses only those tools. That means you shouldn’t waste time using a C++ feature just for the sake of using it— use the feature because it reduces complexity and makes your life easier.
Also, resist the temptation to add in everything under the sun. Not every function needs to be virtual, not every program needs to use exceptions, and not everything needs its own namespace. Pick and choose your weapons carefully, and you’ll be fine.
The Standard Template Library (STL)
Now that I’ve given you a crash course in the C++ features you need to understand this book, it’s time to talk about the other thing you need to know: STL.
What Is the STL and Why Should I Care?
STL is an acronym for the Standard Template Library. Let’s take that apart word-byword. First of all, the STL is standard. The powers that be incorporated it into the American National Standards Institute (ANSI) standard of C++, so any compiler that’s ANSI compliant will have a STL library. This is great, because it means that if you ever need to port your program to Linux or something, you won’t have to scrap all the code that relies on the STL.
Second, the library uses templates. In fact, some of the most heavy-duty take-noprisoners template code I’ve seen resides in the STL. The STL programmers used templates to make the library as useful as possible. For example, they didn’t just want to make one algorithm or container class work with just ints, or just floats, or just chars, so they template-ized the whole thing so that it could work with any class you can dream up.
Finally, the L in STL means library. Real libraries are huge collections of books. The STL is a huge collection of container classes and algorithms. You will probably never use all of the STL in one program, and may never use all of it anywhere. For that reason, the STL is broken down into several components. You use each component by including a specific header file, just like what you’re used to with the C runtime libraries. Usually, you don’t even have to worry about linking the correct STL libraries—the compiler takes care of that for you.
We’re going to concentrate on the two types of STL container classes this book uses: vectors and maps. There are many other container classes (stacks, lists, etc.), and I encourage you to learn more about them, because they can be useful in many situations.
STL Strings
One of the most useful things the STL provides is a string class. C programmers spend a lot of time wrangling fixed-length character arrays—char buf[256] and such. This can often lead to frustrating buffer overrun—or “I tried to put a six character string into a five element array”—errors.
C++ string classes solve this problem. The STL library provides a rather lightweight string class, called string. So, to create a STL string, just type something like this:
std::string strFilename;
You can then assign a value to this string as follows:
strFilename = “C:\\test\\myfile.txt”;
The string class takes care of the details of allocating the right amount of memory to hold the string. You can figure out how long a string is by using the size method:
printf(“The length of the string \”blah\” is: %d”, std::string(“blah”).size());
Also notice in that section of code how I created a temporary string object by passing in a character array to the string constructor.
If you need to convert your string into a character array, use the c_str method:
void Foo(char *strFilename) { /* do something with filename */ } void main(void) { std::string str = “SomeFile.txt”; Foo(str.c_str()); }
That’s essentially the basics of using STL strings. Consult the STL documentation to learn about the more powerful features of STL strings that this article doesn’t use.
CAUTION
Whenever you want to print a string, and you use the %s tag in your printf statement, remember to use the c_str method, otherwise, you’ll get weird printf results, and sometimes program crashes!
For example, don’t do this:
printf(“The string is: %s”, strSomeSTLString); // kablooey!
Instead, do this:
printf(“The string is: %s”, strSomeSTLString.c_str());
STL Vectors
Think of a STL vector as a resizable array. Once you’ve got a STL vector created, you can add as many items to it as you want (until you run out of memory, of course), and you can delete any item inside the array without affecting the others.
Making a STL Vector
To create a new vector, all you need to do is declare a variable of that type. Remember, vector is a template class, so you need to give it the type of stuff you’ll be storing. For example, to create a vector of ints, write something like this:
std::vector<int> m_ArrayOfInts;
To create a vector of chars, write:
std::vector<char> m_ArrayOfChars;
Or to create a vector of your own classes, write:
std::vector<CMyClass> m_ArrayOfMyClasses;
Adding Items
The vector class allows you to put objects into it in a few different ways. First, there’s the most common: Just call the push_back method:
std::vector<int> m_ArrayOfInts; m_ArrayOfInts.push_back(5); // add 5 to array (array is now 5) m_ArrayOfInts.push_back(3); // add 3 to array (array is now 5, 3) m_ArrayOfInts.push_back(4); // add 4 to array (array is now 5, 3, 4)
The push_back method puts the new item at the very end of the array. (In case you’re curious, there’s also pop_back, which removes the last item from the array.) When you push an item into a STL vector, you’re actually making a copy of that item. This is a moot point when dealing with ints, but becomes important if you’re using a vector to store classes. If you want to insert stuff in the middle of the array, you can use insert. The insert method takes two arguments.
CAUTION
Your class needs to implement an operator == overload in order for this to work, and if your class allocates memory or contains pointers to different things, you need to make sure that your overload copies that memory correctly.
The first one specifies where you’d like the item placed. The insert method will place the item immediately before the one specified. The second argument is a reference to the item you want to insert. Here’s an example of insert:
std::vector<int> m_ArrayOfInts; m_ArrayOfInts.push_back(5); // add 5 to array (array is now 5) m_ArrayOfInts.push_back(3); // add 3 to array (array is now 5, 3) // add 4 to the *beginning* of the array m_ArrayOfInts.insert(m_ArrayOfInts.begin(), 4); // array is now 4, 5, 3 // add 7 between 5 and 3 (that is, before element 3) m_ArrayOfInts.insert(m_ArrayOfInts.begin()+2) // array is now 4, 5, 7, 3
The only thing weird in that code is probably the m_ArrayOfInts.begin()+2 stuff. Essentially, the begin method returns an iterator. An iterator is a pointer to whatever type you’re storing; in this case, it’s an int *. The begin method of vector returns an iterator that points to the first thing in our vector. Since we want the third object down, we simply add two to this iterator and pass that as the location
before which we want the new item inserted. STL vectors allow you to insert things in many more ways than I’ve just shown. For example, you can insert a whole range of blank objects, using resize or another insert overload. However, most programmers rarely use more than push_back and the simple insert overload.
TIP
This sounds more complex than it is. Don’t worry, you’ll become more familiar with iterators after you have a chance to play with them in your own programs.
Getting and Editing Items
Once you got some stuff in the vector, you probably want to get at it. You can do this in several ways. First and oftentimes easiest: STL vectors overload the brackets operator, [ ], allowing you to pretend that your vector is really just an array:
std::vector<int> m_ArrayOfInts; m_ArrayOfInts.push_back(5); // add 5 to array (array is now 5) m_ArrayOfInts.push_back(3); // add 3 to array (array is now 5, 3) if (m_ArrayOfInts[0] != 5) { printf(“All is not right in the universe.”); }
You can also loop through the entire vector by doing something like this:
for (std::vector<int>::iterator i = ArrayOfInts.begin(); i != ArrayOfInts.end(); ++i) { printf(“%d “, *i); }
That’s a mammoth for statement, but it comes apart fairly easily. First, we declare an iterator for a vector of integers, name it i, and set it equal to the beginning of the vector. We keep looping so long as i does not equal end. The end method of vector is very similar to begin, except it gives you an iterator that’s one past the end of the array.
TIP
Always use ++i instead of i++ when iterating through a vector. It’s faster, because the compiler does not have to create a temporary object behind the scenes.
So, essentially, we’re looping from the beginning of the vector to the end. Since i is an iterator, and an iterator is just a fancy pointer to our object, all we have to do inside the for loop is de-reference the pointer and use it. If you were using classes, you would probably want to create a reference to that class element, like this:
for (std::vector<CMyClass>::iterator i = ArrayOfClasses.begin(); i != ArrayOfClasses.end(); ++i) { CMyClass &ThisClass = *i; /* do whatever you need to here, using ThisClass */ } // next!
That code makes things a little easier to read, because once you get into your for loop, you can use a reference to the class instead of constantly having to de-reference the iterator.
CAUTION
Be careful if you add new items, shuffle the item order, or delete items while you’re using an iterator to move through the vector.
The insertion and deletion functions may actually invalidate your iterator, which will cause you to go tromping off into memory somewhere, and will probably lead to an access violation. Check the STL docs for more information on how to get around this.
Deleting Items
To delete an item from a vector, use the clear and erase methods. The clear method kills everything in the array:
std::vector<int> m_ArrayOfInts; m_ArrayOfInts.push_back(5); // add 5 to array (array is now 5) m_ArrayOfInts.push_back(3); // add 3 to array (array is now 5, 3) m_ArrayOfInts.clear(); // array is now empty The erase method, on the other hand, allows you to delete a specific item or a range of items. STL vectors provide two overloaded erase methods. The first overload takes one iterator, and simply deletes that item. The second overload takes two iterators, and deletes all items between those iterators, including the first iterator but not the second: <source lang="cpp"> std::vector<int> m_ArrayOfInts; m_ArrayOfInts.push_back(5); // (array is now 5) m_ArrayOfInts.push_back(3); // (array is now 5, 3) m_ArrayOfInts.erase(m_ArrayOfInts.begin()); // (array is now 3) m_ArrayOfInts.push_back(4); // (array is now 3, 4) m_ArrayOfInts.push_back(6); // (array is now 3, 4, 6) // kill everything but the first element m_ArrayOfInts.erase(m_ArrayOfInts.begin()+1, m_ArrayOfInts.end()); // array now contains 3
The code above shows how to use both erase overloads. Again, note that the end method does not return an iterator pointing to the last element of the array. It returns an iterator pointing past the last element of the array.
A Clever Way to Delete Vectors Full of Derived Objects
Many programmers use STL vectors to store pointers to different classes. For example, say you have three classes, CCircle, CTriangle, and CRectangle, all of which derive from a common base class, CShape. Now, let’s say you need to store an arbitrary number of circles, triangles, and rectangles. An elegant way to do this is to create a vector of CShape *’s, dynamically allocate memory for each object, and then add its pointer to the vector, like so:
class CShape { /* blah */ }; class CCircle: public CShape { /* blah */ }; class CTriangle: public CShape { /* blah */ }; class CRectangle: public CShape { /* blah */ }; void main(void) { std::vector<CShape *> shapes; shapes.push_back(new CCircle()); shapes.push_back(new CTriangle()); shapes.push_back(new CRectangle()); // now, how do you delete these shapes? }
So, the question is, “how do you delete the shapes you’ve just created?” This is where the cleverness comes in. For years, I used to write deletion code like this:
for (std::vector<CShape *>::iterator I = shapes.begin(); i != shapes.end(); ++i) { delete i; } shapes.clear();
As it stands, there’s nothing wrong with that code. It gets the job done just great; however, it’s a bit much to write just to nuke a vector.
Yordan Gruchev, a fellow game programmer, has come up with a better way. He has used template programming to create an stlwipe function that encapsulates all of that code. Here’s what stlwipe looks like:
template<class T> void stlwipe(T &t) { //get the first iterator T::iterator i=t.begin(); //iterate to the end and delete items (should be pointers) for(; i!=t.end();++i) delete(*i); //clear the collection (now full of dead pointers) t.clear(); }
See how that works? Now that we have our template function, we can just call stlwipe( shapes) to delete everything from our vector. Pretty slick, isn’t it?

