Constructors, Destructors, and Exceptions - Advanced Programming - C++ All-in-One For Dummies (2009)

C++ All-in-One For Dummies (2009)

Book IV

Advanced Programming

Chapter 3: Constructors, Destructors, and Exceptions

In This Chapter

Writing constructors

Using different kinds of constructors

Writing destructors

Understanding the order that takes place in construction and destruction

Throwing and catching exceptions

Now’s the time to seriously master C++. In this chapter, we talk about three vital topics: constructors, destructors, and exceptions. Fully understanding what goes on with constructors and destructors is very important. The better you understand how constructors and destructors work, the less likely you are to write code that doesn’t function the way that you expected and the more likely you are to avoid bugs.

Exceptions are important also in that they let you handle error situations — that is, you can handle problems when they do come up.

Many developers feel that constructors, destructors, and exceptions are extremely simple. In fact, many developers would doubt that these three topics could fill an entire chapter, but they can. After you read this chapter, you should have a good mastery of constructors, destructors, and exceptions.

So without further ado, you can begin to construct your reading as you destruct any old ways of programming, without exception.

Constructing and Destructing Objects

Classes are goofy little things. They like to have some say in how their instances get started. But that’s okay. We’re programmers, and we like to do what the computer wants us to do (as opposed to the other way around). And so the great founders of the C++ language gave us constructors. Constructors are member functions that the program calls when it creates an instance. Destructors, on the other hand, are member functions that the program calls when it deletes an instance.

A single class can have multiple constructors. In fact, several kinds of constructors are available. There aren’t as many kinds of destructors. (In fact, there’s really only one.) In the sections that follow, we give you all the necessary information so that, when your classes want constructors, you will be able to happily add them.

image If you see some older C++ code, you are more than likely to see the word virtual before a constructor in a class definition. The idea was that you can override a constructor when you derive a new class, so you should make it virtual. However, in ANSI C++, this construction is not right. You cannot make a constructor virtual. If you put the word virtual before a constructor, you get a compiler error. If you see an older class that has a virtual constructor, create constructors as you normally would in the derived classes, and all will be fine. (Unfortunately, all is not fine for those million or so C++ programmers who spent years writing code that had the word virtual before a constructor. They get to practice using that Backspace key.)

Overloading constructors

You are allowed to put multiple constructors in your class. The way the user of your class chooses a constructor is by setting up the parameters in the variable declaration. Suppose you have a class called Clutter, and suppose you see the following two lines of code:

Clutter inst1(“Jim”);

Clutter inst2(123, “Sally”);

These two lines have different types of parameters in the list. Each one is making use of a different constructor for the single class.

You can put multiple constructors in your class. The process of putting multiple constructors is called overloading the constructors. Here’s an example of a Clutter class that has two constructors:

class Clutter

{

protected:

string ChildName;

int Toys;

public:

Clutter(int count, string name)

{

ChildName = name;

Toys = count;

}

Clutter(string name)

{

ChildName = name;

Toys = 0;

}

};

The compiler will figure out which overloaded constructor to use based on the parameters. Therefore, the overloaded constructors must differ in their parameter lists — specifically, this means the types of parameters; just changing the names doesn’t count! If the parameter lists don’t differ, the compiler won’t be able to distinguish them, and you will get an error when it tries to compile the class definition.

image If your constructor doesn’t have a parameter provided by other constructors, you should initialize the associated variable within the constructor code. For example, the second constructor doesn’t include a parameter for Toys, so the constructor code initializes this variable to 0. As an alternative, you can use an initializer as described in the “Initializing members” section of the chapter.

imageHaving multiple constructors makes your class much more flexible and easy to use. Multiple constructors give the users of your class more ways to use the class, allowing them to configure the instances differently, depending on their situations. Further, the constructors force the user to configure the instances only in the ways your constructors allow.

Initializing members

When C++ originally came out, any time you wanted to initialize a member variable, you had to put it inside a constructor. This created some interesting problems. The main problem had to do with references. You can put reference variables in a class, but normally reference variables must be initialized. You can’t just have a reference variable floating around that doesn’t refer to anything. But if you put a reference variable inside a class and create an instance of the class, the program will first create the instance and then call the constructor. Even if you initialize the reference in the first line of the constructor, there’s still a moment when you have an uninitialized reference! Oh, what to do, what to do?

The ANSI standard uses a single approach for setting up member variables: initializers. An initializer goes on the same line as the constructor in the class definition; or, if the constructor isn’t inline, the initializer goes with the constructor in the code outside the class definition.

Here’s an example where we have the initializers right inside the class definition:

class MySharona

{

protected:

int OneHitWonders;

int NumberRecordings;

public:

MySharona() : OneHitWonders(1), NumberRecordings(10) {}

};

When you create an instance of this class, the OneHitWonders member gets the value 1 and the NumberRecordings member gets the value 10. Note the syntax: The constructor name and parameter list (which is empty in this case) is followed by a single colon. The member variables appear after that, each followed by an initial value in parentheses. Commas separate the member variables.

After the member variables is the open brace for any code you would want in the constructor. In this case, we had no code, so we immediately put a closing brace.

image You can put any of the class member variables in the initializer list, but you don’t have to include them all. If you don’t care to initialize some, you don’t have to. Note also that you cannot put inherited members in the initializer list; you can include only members that are in the class itself.

You can also pass these initial values in through the constructor. Here’s a slightly modified version of this same class. This time the constructor has a parameter that we save in the NumberRecordings member:

class MySharona

{

protected:

int OneHitWonders;

int NumberRecordings;

public:

MySharona(int Records) : OneHitWonders(1),

NumberRecordings(Records) {}

};

imageBy associating an initializer list with a constructor, you can have different initializers with different constructors. You are not limited to initializing the data the same way for all your constructors.

You may have noticed that the member initialization follows a format similar to the way you initialize an inherited constructor. Take a look at how we’re calling the base class constructor in this code:

class MusicInfo

{

public:

int PhoneNumber;

MusicInfo(int Phone) : PhoneNumber(Phone) {}

};

class MySharona : public MusicInfo

{

protected:

int OneHitWonders;

int NumberRecordings;

public:

MySharona(int Records) : OneHitWonders(1),

NumberRecordings(Records),

MusicInfo(8675309) {}

};

In the MySharona class, the member variables get initialized, and the base class constructor gets called, all in the initialization. The call to the base class constructor is this portion:

MusicInfo(8675309)

But note that we’re passing a number into the constructor. The MusicInfo constructor takes a single number for a parameter, and it uses the number it receives to initialize the Phone member:

MusicInfo(int Phone) : PhoneNumber(Phone) {}

Therefore, every time someone creates an instance of the class MySharona, the inherited PhoneNumber member is automatically initialized to 8675309.

Thus, you can create an instance of MySharona like this:

MySharona CD(20);

This instance starts out having the member values OneHitWonders = 1, NumberRecordings = 20, and Phone = 8675309. The only thing that the user can specify here for a default value is the NumberRecordings member. The other two members are set automatically by the class.

However, you don’t have to do it this way. Perhaps you want the users of this class to be able to specify the PhoneNumber when they create an instance. Here’s a modified form that does it for you:

class MusicInfo

{

public:

int PhoneNumber;

MusicInfo(int Phone) : PhoneNumber(Phone) {}

};

class MySharona : public MusicInfo

{

protected:

int OneHitWonders;

int NumberRecordings;

public:

MySharona(int Records, int Phone) : OneHitWonders(1),

NumberRecordings(Records), MusicInfo(Phone) {}

};

Look carefully at the difference: The MySharona class now has two parameters. The second is an integer, and we pass that one into the base class through the portion:

MusicInfo(Phone)

So to use this class, you might do something like this:

MySharona CD(20, 5551212);

This code snippet creates an instance of MySharona, with the members initialized to OneHitWonders = 1, NumberRecordings = 20, and PhoneNumber = 5551212.

If you have overloaded constructors, you can have different sets of initializations. For example, take a look at yet one more modification to the class:

class MusicInfo

{

public:

int PhoneNumber;

MusicInfo(int Phone) : PhoneNumber(Phone) {}

};

class MySharona : public MusicInfo

{

protected:

int OneHitWonders;

int NumberRecordings;

public:

MySharona(int Records, int Phone) : MusicInfo(Phone),

OneHitWonders(1), NumberRecordings(Records) {}

MySharona(int Records) : MusicInfo(8675309),

OneHitWonders(1), NumberRecordings(Records) {}

};

Now this class has two constructors. We combined the previous two versions, so now you can use either constructor. You can, then, have the following two variables, for example, each using a different constructor:

MySharona CD(20, 5551212);

MySharona OldCD(30);

cout << CD.PhoneNumber << endl;

cout << OldCD.PhoneNumber << endl;

When you run the cout lines, they have different values for the PhoneNumber member. The first passes a specific value; the second accepts a default value:

5551212

8675309

image You should initialize the base class values first. Otherwise, the compiler is likely to display warning messages when you compile the application.

imageIf the only real difference in the different constructors is whether or not the user supplies a value (as was the case in the previous example), you can use a slightly better approach. Constructors (and any function in C++, really) can have default values. The following example shortens the previous ones by using default values. The result is the same:

class MusicInfo

{

public:

int PhoneNumber;

MusicInfo(int Phone) : PhoneNumber(Phone) {}

};

class MySharona : public MusicInfo

{

protected:

int OneHitWonders;

int NumberRecordings;

public:

MySharona(int Records, int Phone=8675309) :

MusicInfo(Phone), OneHitWonders(1),

NumberRecordings(Records) {}

};

imageIn the preceding code, the second parameter to the constructor has an equals sign and a number after it. That means the user of the class doesn’t have to specify this parameter. If the parameter is not present, it automatically gets the value 8675309.

image You can have as many default parameters as you want in a constructor or any other function, but the rule is that the default parameters must come at the end. After you have a default parameter, all the parameters that follow must have a default value. Therefore, the following type of code is not allowed:

MySharona(int Records = 6, int Phone) :

MusicInfo(Phone), OneHitWonders(1),

NumberRecordings(Records) {}

There’s a practical reason for this: When the user calls the constructor (by creating a variable of type MySharona, there is no way to leave out just a first parameter and have only a second one. It’s not possible, unless C++ were to allow an empty parameter followed by a comma, as inMySharona(,8675309). But that’s not allowed.

Adding a default constructor

A default constructor is a constructor that takes no parameters. You can have a default constructor in a class in either of two ways: by coding it or by letting the compiler implicitly build one for you. By implicitly build one for you, we mean that you don’t actually have a constructor in your code, but the compiler gives you one when it compiles the code for you.

You’ve probably seen a default constructor before. This class has no constructor, so the compiler generates an implicit one for you. It works like this:

class Simple

{

public:

int x,y;

void Write()

{

cout << x << “ “ << y << endl;

}

};

Of course, the preceding class doesn’t do much. It’s the same as this:

class Simple

{

public:

int x,y;

void Write()

{

cout << x << “ “ << y << endl;

}

Simple() {}

};

However, recognizing that the default constructor is there is important. And you need to realize when the compiler doesn’t create one automatically because you may run into some problems. Take a look at this modified version of the class:

class Simple

{

public:

int x,y;

void Write()

{

cout << x << “ “ << y << endl;

}

Simple(int startx) { x = startx; }

};

We included in this code our own constructor that takes a parameter. After we do this, the class no longer gets an implicit default constructor. If we have a line later like this:

Simple inst;

the compiler will give us an error message like this:

In function `int main()’

error: no matching function for call to `Simple::Simple()’

note: candidates are: Simple::Simple(const Simple&)

note: Simple::Simple(int)

Yet, if we take out the constructor we added (so it goes back to an earlier example), this error goes away! Therefore, if you provide no constructors, the compiler gives you an implicit default constructor.

imageIf you’re using the CodeBlocks compiler, you obtain some helpful additional information. In this case, you see two note lines that tell you about the available constructor candidates. You can use this information as part of your troubleshooting efforts.

Now here’s where you could run into trouble: Suppose you build a class and provide no constructors for it. You give the class to other people to use. They’re using it in their code, all happy, making use of the default constructor. Then one day somebody else (not you — you don’t make mistakes) decides that he wants to enhance the class by adding a special constructor with several parameters. The rogue programmer adds the constructor and then makes use of it. Mr. Rogue thinks all is fine, because he’s using only his new constructor. But little does he know: All the other people who were using the implicit default constructor suddenly start getting compiler errors!

Believe it or not, we have seen this happen. One day, all of a sudden, your code won’t compile. Any time you try to create an instance of a class, you start getting errors stating that the compiler can’t find Simple::Simple(). Oops. Somebody changed it.

But you can avoid this problem by making sure that you explicitly include a default constructor, even if it does nothing:

class Simple

{

public:

int x,y;

void Write()

{

cout << x << “ “ << y << endl;

}

Simple() {}

};

Then when Mr. Rogue adds his own constructor, the default constructor will still be there (assuming, of course, that he doesn’t remove it. But if he does, move him to that nice secluded inner office that has no windows and no doors). When he adds his extra constructor, he will be overloading it:

class Simple

{

public:

int x,y;

void Write()

{

cout << x << “ “ << y << endl;

}

Simple() {}

Simple(int startx) { x = startx; }

};

Note that now this class has two constructors! And all will be happy, because everybody’s code will still compile.

Functional constructors

Every once in a while, you may come across something that looks like this:

Simple inst = Simple(5);

What is that? It looks like a function call. Or it looks like the way you would declare a pointer variable, except there’s no asterisk and no new word. So what is it? It’s a functional syntax for calling a default constructor. The right side creates a new instance of Simple, passing 5 into the constructor. Then this new instance gets copied into the variable called inst.

This approach can be handy if you’re creating an array of objects, where the array contains actual objects, not pointers to objects:

Simple MyList[] = { Simple(1), Simple(50), Simple(80),

Simple(100), Simple(150) };

The approach seems a little strange because the variable MyList is not a pointer, yet you’re setting it equal to something on the right. But this approach is handy because every once in a while you need a temporary variable. Listing 3-1 shows how you can use the functional syntax to create a temporary instance of the class string.

Listing 3-1: Creating Temporary Instances with Functional Constructors

#include <iostream>

#include <string>

using namespace std;

void WriteMe(string str)

{

cout << “Here I am: “ << str << endl;

}

int main()

{

WriteMe(string(“Sam”));

return 0;

}

When you compile and run this, you see this output:

Here I am: Sam

In main, we created a temporary instance of the string class. (Remember, string is a class!) But as it turns out, an even shorter version of this is available. If we had called WriteMe, we could have just done this:

WriteMe(“Sam”);

This code works out well because you don’t even feel like you’re working with a class called string. The parameter just seems like a basic type, and you’re passing a character array, Sam. However, the parameter is an instance of a class. Here’s how the code works. Suppose you have a class like this, and a function to go with it:

class MyNumber

{

public:

int First;

MyNumber(int TheFirst) : First(TheFirst) {}

};

void WriteNumber(MyNumber num)

{

cout << num.First << endl;

}

(WriteNumber is not a member of MyNumber.) You can do any of the following calls to WriteNumber.

MyNumber prime = 17;

WriteNumber(prime);

WriteNumber(MyNumber(23));

WriteNumber(29);

The first call uses a previously declared variable of type MyNumber. The second call creates a temporary instance, passing the value 23 into the constructor. The third one also creates a temporary instance, but it does so implicitly! The output from this example is

17

23

29

image You may wonder when your temporary variables get destroyed. For instance, if you call WriteNumber(MyNumber(23));, how long does the temporary MyNumber instance live on? The ANSI standard proudly proclaims that the instance will get deleted at the end of the full expression. In other words, after the line is done, the temporary instance will be done for.

Be careful when using implicit temporary objects. Consider the following class and function:

class MyName

{

public:

string First;

MyName(string TheFirst) : First(TheFirst) {}

};

void WriteName(MyName name)

{

cout << “Hi I am “ << name.First << endl;

}

Seems straightforward. The MyName constructor takes a string, so it seems like we should be able to do this when we call the WriteName function:

WriteName(“George”);

Except we can’t. The compiler gives us an error message:

In function `int main()’

error: conversion from `const char*’ to non-scalar type `MyName’ requested

Here’s the problem: The compiler got shortsighted. The compiler considers the type of the string constant a const char * (that is, a pointer to a const character, or really a constant character array). Although we don’t have any constructors that take a const char * parameter, we do have one that takes a string, and it has a constructor that takes a const char * parameter. Unfortunately, the compiler doesn’t fall for that, and it complains. So we have to adjust our function call just a tad, like so:

WriteName(string(“George”));

And this time it works. Now we explicitly create a temporary string instance. And by using that, we implicitly create a temporary instance of our own class, MyName. It would be nice if the compiler could wade through this and implicitly create the string instance for us, but it doesn’t seem to want to. Oh well. Calling WriteName(string(“George”)); works well enough for us.

Calling one constructor from another

If you have some initialization code and you want several constructors to call it, you might try putting the code in one constructor and then having the other constructors call the constructor that has the initialization code.

Unfortunately, that won’t work. Some things in life we just can’t have, and this is one of them. If you have a constructor and write code to call another constructor, such as this

CallOne::CallOne(int ax)

{

y = 20;

CallOne();

}

where CallOne is your class, then this will compile but won’t behave the way you may expect. The line CallOne(); is not calling a constructor for the same instance! The compiler treats this line as a functional constructor. Thus the line creates a separate, temporary instance. And then at the end of the line CallOne(), the program deletes the instance.

You can see this behavior with the following class:

class CallOne

{

public:

int x,y;

CallOne();

CallOne(int ax);

};

CallOne::CallOne()

{

x = 10;

y = 10;

}

CallOne::CallOne(int ax)

{

y = 20;

CallOne();

}

When you create an instance by using the second constructor like this, the value of the y member of the instance will be 20, not 10:

CallOne Mine(10);

To people who don’t know any different, it may look like the y would first get set to 20 in the second constructor, and then the call to the default constructor would cause it to get changed to 10. But that’s not the case: The second constructor is not calling the default constructor for the same object; it’s creating a separate, temporary instance.

image If you have common initialization code that you want in multiple constructors, put the code in its own private or protected function (called, for example, Init), and have each constructor call the Init function. If you have one constructor call another constructor, it won’t work. The second constructor will be operating on a separate instance.

Copying instances with copy constructors

One nice thing about C++ is that it lets you copy instances of classes. For example, if you have a class called Copyable, you can write code like this:

Copyable first;

Copyable second = first;

This will create two instances, and second will be a duplicate of first. The program will accomplish this by simply copying all the member variables from first to second.

That works pretty well, except sometimes you want to customize the behavior just a bit. For example, you may have a member variable that contains a unique ID for each instance. In your constructor, you may have code that generates a unique ID. The problem is that the previous sample will not call your constructor: It will just make a duplicate of the object. Thus, your two objects will have the same number for their supposedly “unique” IDs. So much for diversity.

So if you want to have control over the copying, you can create a copy constructor. A copy constructor is just a constructor that takes as a parameter a reference to another instance of the same class, as in this example:

Copyable(const Copyable& source);

When you copy an instance, your program will call this constructor. The parameter to this constructor will be the instance you are copying. Thus, in the case of Copyable second = first;, the source parameter will be first. And because it’s a reference (which is required for copy constructors), you can access its members by using the dot notation (.) rather than the pointer notation (->).

Listing 3-2 is a complete program that demonstrates copy constructors.

Listing 3-2: Customizing the Copying of Instances

#include <iostream>

using namespace std;

class Copyable

{

protected:

static int NextAvailableID;

int UniqueID;

public:

int SomeNumber;

int GetID() { return UniqueID; }

Copyable();

Copyable(int x);

Copyable(const Copyable& source);

};

Copyable::Copyable()

{

UniqueID = NextAvailableID;

NextAvailableID++;

}

Copyable::Copyable(int x)

{

UniqueID = NextAvailableID;

NextAvailableID++;

SomeNumber = x;

}

Copyable::Copyable(const Copyable& source)

{

UniqueID = NextAvailableID;

NextAvailableID++;

SomeNumber = source.SomeNumber;

}

int Copyable::NextAvailableID;

int main()

{

Copyable take1 = 100;

Copyable take2;

take2.SomeNumber = 200;

Copyable take3 = take1;

cout << take1.GetID() << “ “

<< take1.SomeNumber << endl;

cout << take2.GetID() << “ “

<< take2.SomeNumber << endl;

cout << take3.GetID() << “ “

<< take3.SomeNumber << endl;

return 0;

}

Go ahead and run this application to see how it works. The output from this example is

0 100

1 200

2 100

We need to tell you two things about the copy constructor in this code. First, we included const in the parameter of the copy constructor. That’s because of a small rule in C++ where, if you have a constant instance, you won’t be able to copy it otherwise. If we left off const, this line would not compile properly. And as it happens, that’s the case in the following line:

Copyable take1 = 100;

The second thing we need to tell you is that, in the code for the copy constructor, we had to manually copy the member variables from one instance to the other. That’s because now that we’re supplying our own copy constructor, the computer will not copy the members as it would when we supply no copy constructor at all.

Listing 3-2 uses a static member to keep track of what the next available UniqueID is. Remember that a class shares a single static member among all instances of the class. Therefore, you have only one instance of NextAvailableID, and it’s shared by all the instances of classCopyable.

image For a long, happy life

♦ Put a const in your copy constructor.

♦ Copy the items manually.

When constructors go bad: failable constructors?

Suppose that you’re writing a class that will connect to the Internet and automatically download the latest weather report for the country of Upper Zamboni. The question is this: Do you put the code to connect to the Internet in the constructor or not?

People are often faced with this common design issue. Putting the initialization code in the constructor provides many advantages. For one, you can just create the instance without having to first create it and then call a separate member function that does the initialization. And in general, that works fine.

But what if that initialization code can result in an error? For example, suppose that the constructor is unable to connect to the Internet. Then what? Remember: A constructor doesn’t return a value. So you can’t have it return, for example, a bool that would state whether it successfully did its work.

You have many choices for this, and different people seem to have rather strong opinions about which choice is best. (Programmers with strong opinions? Now there’s an unlikely concept.) Here are the ones we’ve seen:

♦ Just don’t do it: Write your constructors so they create the object but don’t do any work. Instead, put the work code in a separate member function, which can return a bool representing whether it was successful.

♦ Let the constructor do the work: If the work fails (such as it can’t connect to the Internet), have the constructor save an error code in a member variable. When you create an instance, you can check the member variable to see whether it works.

♦ Let the constructor do the work: If the work fails, throw an exception. In your code, then, you would wrap the creation of the instance with a try block and include an exception handler. (See “Programming the Exceptions to the Rule,” later in this chapter, for more information on try blocks and exception handlers.)

image We don’t like this choice. If other people are using the class that we wrote, we don’t want them to have to go through the motions of wrapping it in a try block and exception handler. But other than being a nice guy, there’s a practical reason for avoiding this choice: If we have teammates who are beginners at programming, they may just skip that part. “Oh shoot. It’ll never fail,” might be their attitude. And when it does fail on a customer’s computer (if it can, then it will, Mr. Murphy!) the customer will be very unhappy that his or her program couldn’t connect to the Internet and crashed.

Destroying your instances

Although constructors are versatile and it seems like people could write entire books on them (good for family story time reading), destructors are simple, and there’s not a whole lot to say about them. But you do need to know some information to make them work properly.

First, destructors don’t get parameters, and (like constructors) they do not have return types. So not much more to say about that.

Suppose you have a class that contains, as members, instances of other classes. When you delete an instance of the main class, will the contained instances be deleted automatically? That depends. If your class contains actual instances (as opposed to pointers), they will get deleted. Look at this code:

class LittleInst

{

public:

int MyNumber;

~LittleInst() { cout << MyNumber << endl; }

};

class Container

{

public:

LittleInst first;

LittleInst *second;

Container();

};

Container::Container()

{

first.MyNumber = 1;

second = new LittleInst;

second->MyNumber = 2;

}

We have two classes, LittleInst and Container. The Container class holds an instance of LittleInst (the member variable called first) and a pointer to LittleInst. In the constructor we set up the two LittleInst instances. For first, it already exists, and all we have to do is set up its MyNumber member. But second is just a pointer, so we have to create the instance before we can set up its MyNumber member. Thus, we have two instances, one a pointer and one a regular instance.

Now suppose you use these classes like so:

Container *inst = new Container;

delete inst;

We gave Container no destructor. So will its members, first and second, get destroyed? Here’s what we see after these two lines run:

1

That’s the output from the LittleInst destructor. The number 1 goes with the first member. So you can see that first was deleted, but second wasn’t.

image Here’s the rule: When you delete an instance of a class, the members that are direct (that is, not pointers) are deleted as well. Any pointers, however, you must manually delete in your destructor (or elsewhere).

Sometimes, you may want an object to hold an instance of another class but want to keep the instance around after you delete the containing object. In that case, you wouldn’t delete the other instance in the destructor.

Here’s a modification to the Container class that deletes the second instance:

class Container

{

public:

LittleInst first;

LittleInst *second;

Container();

~Container() { delete second; }

};

Then when you run these two lines again

Container *inst = new Container;

delete inst;

you see this output, which deletes both instances:

2

1

In the preceding output, you can see that it deleted the second instance first. The reason is that the program calls the destructor before it destroys the direct members. In this case, when we deleted our Container instance, the program first called our destructor before deleting our firstmember. That’s actually a good idea, because in the code for our destructor, we may want to do some work on our member variables before they get wiped out.

Virtually inheriting destructors

Unlike constructors, you can (and should) make destructors virtual. The reason is that you can pass an instance of a derived class into a function that takes a base class, like this:

void ProcessAndDelete(DeleteMe *inst)

{

cout << inst->Number << endl;

delete inst;

}

This function takes an instance of class DeleteMe, does some work on it, and deletes it. Now suppose you have a class derived from DeleteMe, say class Derived. Because of the rules of inheritance, you’re allowed to pass the instance of Derived into this function. But by the rules of polymorphism, if you want the ProcessAndDelete function to call an overloaded member function of Derived, you need to make the member function virtual. And that’s the case with all destructors as well. Listing 3-3 shows this.

Listing 3-3: Making the Destructors Virtual

#include <iostream>

using namespace std;

class DeleteMe

{

public:

int Number;

virtual ~DeleteMe();

};

class Derived : public DeleteMe

{

public:

virtual ~Derived();

};

DeleteMe::~DeleteMe()

{

cout << “DeleteMe::~DeleteMe()” << endl;

}

Derived::~Derived()

{

cout << “Derived::~Derived()” << endl;

}

void ProcessAndDelete(DeleteMe *inst)

{

cout << inst->Number << endl;

delete inst;

}

int main()

{

DeleteMe *MyObject = new(Derived);

MyObject->Number = 10;

ProcessAndDelete(MyObject);

return 0;

}

Ordering your constructors and destructors

When you have constructors and destructors in a base and derived class and you create an instance of the derived class, remember the ordering: The computer first creates the members for the base class, and then the computer calls the constructor for the base class. Next, the computer creates the members of the derived class, and then the computer calls the constructor for the derived class.

The order for destruction is opposite. When you destroy an instance of a base class, first the computer calls the destructor for the derived class and then deletes the members of the derived class. Next the computer calls the destructor for the base class and then deletes the members of the base class.

When you run this program, thanks to the cout calls in the destructors, delete is calling the destructor for Derived (which in turn calls the base class destructor). Here’s the output:

10

Derived::~Derived()

DeleteMe::~DeleteMe()

The first line is the output from the ProcessAndDelete function. The middle line is the output from the Derived destructor, and the third line is the output from the DeleteMe destructor. We passed in a Derived instance, and the program called the Derived destructor.

Now try this: Remove virtual from the DeleteMe destructor:

class DeleteMe

{

public:

int Number;

~DeleteMe();

};

Then when you compile and run the program, the program calls the base class destructor. Because the ProcessAndDelete function takes a DeleteMe instance, you see this output:

10

DeleteMe::~DeleteMe()

In the preceding example, the destructor isn’t virtual; it’s not able to find the proper destructor when you pass in a Derived instance. So it just calls the destructor for whatever type is listed in the parameter.

imageGetting into the habit of always making your destructors virtual is a good idea. That way, if somebody else writes a function, such as ProcessAndDelete, you can be assured that his or her function will automatically call the correct destructor.

Programming the Exceptions to the Rule

An exception is a bad situation that occurs in your software, causing your program to have to handle the bad situation. For example, if you try to write to a file but somehow that file got corrupted and you can’t, the operating system might throw an exception. Or you might have a function that processes some data, and if the function encounters corrupted data, it might throw an exception.

Exceptions were new to C++; they did not exist in C. People were a little suspicious of them when they first came out, and some people even consider them to be Bad (that’s with a capital B). The reason is this: People who oppose exceptions feel that writing code that relies too heavily on exceptions is too easy. But you should use them because they help you handle situations that you might not otherwise anticipate.

Listing 3-4 is an example of a function that we wrote that throws an exception and an entire program that uses the function.

Listing 3-4: Throwing and Catching Exceptions

#include <iostream>

#include <string>

using namespace std;

void ProcessData()

{

throw new string(“Oops, I found some bad data!”);

}

int main()

{

try

{

ProcessData();

cout << “No problems!” << endl;

}

catch (string *excep)

{

cout << “Found an error. Here’s the message.”;

cout << endl;

cout << *excep;

cout << endl;

}

cout << “All finished.” << endl;

return 0;

}

Go ahead and run this application. You see the following text as output:

Found an error. Here’s the message.

Oops, I found some bad data!

All finished.

Look closely at what this program does. In main, there’s a call to ProcessData, which we put inside a try block. Because the call is inside a try block, the computer calls the function; and if the function throws an exception, the program automatically comes back out of the function and goes into the catch block. The catch block receives the item that was thrown as a parameter, much like a parameter to a function.

But if the ProcessData function didn’t encounter any problems and therefore didn’t throw an exception, the function will complete its work and the program will continue with the code after the function call. In this case, one more line is inside the try block. If there was no exception, upon completion of the ProcessData function, the computer will do the cout line after the ProcessData call.

Think of an exception handler as a way to catch errors: If an exception gets thrown, your program can catch the error by including a catch block.

After the try/catch block is complete, the program will run any lines that follow, regardless of whether or not an exception was present. Thus, in all cases, Listing 3-4 will execute the line

cout << “All finished.” << endl;

Now in the listing, note that our ProcessData function calls throw, meaning that it generates an exception. Normally, you probably wouldn’t just have a function throw an exception for no reason, as this function does (unless you’re trying to have fun with your users), but we do that just to demonstrate how the exceptions work. And besides, this is fun!

This particular throw looks like this:

throw new string(“Oops, I found some bad data!”);

We create a new string instance, and that’s what we throw. You can create an instance of any class you want, and it can be either a pointer or a direct instance, depending on whether you prefer to work with pointers or references (it’s your choice).

Now look at the catch block in Listing 3-4. Notice that it starts with this:

catch (string *excep)

Because in the function we threw a pointer to a string instance, here we catch a pointer to a string instance. Everything must match.

You can have more than one catch block. Suppose different types of exceptions could get thrown. For example, you might have another function like this:

void ProcessMore()

{

throw new int(10);

}

Whereas the other function threw a pointer to a string, this throws a pointer to an integer. Watch out! Lots of things getting thrown around!

Then, when you call the two functions, your try/catch block can look like this:

try

{

ProcessData();

ProcessMore();

cout << “No problems!” << endl;

}

catch (string *excep)

{

cout << “Found an error. Here’s the message.”;

cout << endl;

cout << *excep;

cout << endl;

}

catch (int *num)

{

cout << “Found a numerical error. Here it is.”;

cout << endl;

cout << *num;

cout << endl;

}

cout << “All finished.” << endl;

If you add this code and the ProcessMore function to Listing 3-4, you probably want to comment out the throw line from the ProcessData function if you want to see this program handle the integer exception. That’s because the execution of the lines in the try block cease as soon as a throw statement occurs, and control will be transferred to the appropriate catch block. Which catch block depends on the type of the object thrown.

Throwing direct instances

You can throw a direct instance that is not a pointer. In your throw line, you would do this

void ProcessData()

{

throw string(“Oops, I found some bad data!”);

}

or this

void ProcessMore()

{

throw 10;

}

Instead of throwing pointers, we’re throwing the object or value itself. In the catch block, then, you can catch the type itself without a pointer:

try

{

ProcessData();

ProcessMore();

}

catch (string excep)

{

cout << excep;

}

catch (int num)

{

cout << num;

}

Or if you prefer, you can use references in the catch block. (The throw line does not change.)

try

{

ProcessData();

ProcessMore();

}

catch (string &excep)

{

cout << excep;

}

catch (int &num)

{

cout << num;

}

imageYou may notice something just a little strange. For the integer version, the throw statement looks like this:

throw 10;

That is, the line of code is throwing a value, not an object. But the catch line looks like this:

catch (int &num) {

The catch statement is catching a reference. Normally you can have references to only variables, not values! But it works here because inside the computer, the program makes a temporary variable, and that’s what you’re referring to in the catch block. So all is fine.

Catching any exception

If you want to write a general catch handler that will catch any exception and you don’t care to actually catch the object that was thrown, you can write your handler like this:

try

{

ProcessData();

ProcessMore();

cout << “No problems!” << endl;

}

catch (...)

{

cout << “An unknown exception occurred.” << endl;

}

That is, instead of putting what is effectively a function parameter in the catch header, you just put three dots, called an ellipsis.

image Some word processors (Microsoft Word being one) can transform three typed periods into a single ellipses character. Don’t paste that character into the code editor because the compiler won’t know what to do with it. Instead, type three periods.

You can use the ellipses also as a general exception catcher in addition to your other handlers. Here’s an example:

try

{

ProcessData();

ProcessMore();

cout << “No problems!” << endl;

}

catch (string excep)

{

cout << “Found an error. Here’s the message.”;

cout << endl;

cout << excep;

cout << endl;

}

catch (int num)

{

cout << “Found a numerical error. Here it is.”;

cout << endl;

cout << num;

cout << endl;

}

catch (...)

{

cout << “An unknown exception occurred.” << endl;

}

image If your function calls throw an exception and you don’t have any exception handler for it (because your catch blocks don’t handle the type of exception being thrown or you don’t have any try/catch blocks), your program will stop. The gcc compiler that comes with CodeBlocks, Dev-C++, MinGW, and Cygwin prints the following message on the console and then immediately terminates the program:

abnormal program termination

That’s not good at all! Imagine the looks on your users’ faces if they saw this. We know that we wouldn’t want to be standing there with them, knowing that we’re the ones who wrote the program.

Visual C++ also prints this same message but shows it in a message box. Borland C++ Builder shows the same message, too, and writes it to the console.

imageTwo programming rules keep your users happily ignorant of exceptions:

♦ Know when you are calling a function that could throw an exception.

♦ When you are calling a function that could throw an exception, include an exception handler.

It doesn’t matter how deep the exception is thrown; somewhere, somebody needs to catch it. A function could call a function that calls a function that calls a function that calls a function that throws an exception. If no intermediate function has an exception handler, put one in your outer function.

Rethrowing an exception

When inside a catch block, a throw statement without anything after it will simply rethrow the same exception. Although this may seem a bit convoluted (and indeed it can be), you may have a function that contains a try/catch block. But this function might also be called by another function that has a try/catch block. In other words, you might have something like this:

#include <iostream>

using namespace std;

void Inner()

{

throw string(“Error!”);

}

void Outer()

{

try

{

Inner();

}

catch (string excep)

{

cout << “Outer caught an exception: “;

cout << excep << endl;

throw;

}

}

int main()

{

try

{

Outer();

}

catch (string excep)

{

cout << “main caught an exception: “;

cout << excep << endl;

}

return 0;

}

In the preceding code, main calls Outer. Outer, in turn, calls Inner. Inner throws an exception, and Outer catches it. But main also wants to catch the exception. So we had inner rethrow the exception. You do that by calling throw without anything after it, like this:

throw;

When you run this program, you see the following output.

Outer caught an exception: Error!

main caught an exception: Error!