There are a number of special events in the life-cycle of a class, the most important ones being the creation of a class object and the deletion of a class object. These events are so important that Visual C++ provides some special functions to help.
Whenever a class is made, a special function called the constructor is called.
When you declare a variable, it is a good practice to initialize its value so that if you do not set it again in the program, the rogue value it has does not cause problems later. The same is true of variables inside a class. When a new class object is
declared, it is best if all the members of the class are set up correctly. You could write a function, Initialize(), which would set up the correct values. However, you are now depending on the programmer remembering to write the correct code. If the
Initialize() function is not called, the class code might not work properly.
Visual C++ can call a function automatically when a class object is made, either by declaration or dynamically by calling new. This function is called the constructor. So that Visual C++ and the programmer know which member function is meant to be
called, there is a special convention for constructor functions: They are called exactly the same name as the class, and they have no return type. Look at the following example:
include <iostream.h> class Example { public: Example(); }; Example::Example() { cout << "An example has been made!"; } void main() { Example e; }
This would output the following:
An example has been made!
It will seem very strange that a program with only a declaration can output data. A constructor is just like any function except that it is called automatically. Normally, you write code to ensure that values are correctly set and any memory required is
allocated. Typically, you set pointers to zero if you are not going to use them immediately. Then, in functions called later, you can check to see whether any memory has been allocated.
Definition
When you declare a constructor with no parameters, it is called the default constructor.
Visual C++ calls another special member function, the destructor, when an object is destroyed.
If you think about the typical sequence of events in the simple programs you've seen so far, they consist of main functions that do the following:
Sometimes the tidy up step is not necessary, or rather Visual C++ does it for us. The same three steps are required for a class. You have seen how to initialize. You have also seen how to do work, by calling member functions. Now you need to see how to
tidy up. Visual C++ automatically calls another function just before an object is destroyed. This function is called a destructor. It is declared almost exactly like the default constructor, except you place a ~ (tilde) in front of the class name. (There
is no magic here. Visual C++ simply looks in the class definition for this name when looking to see whether a destructor has been provided.) Again, a destructor has no return value, and it receives no parameters.
include <iostream.h> class Example { public: Example(); ~Example(); }; Example::Example() { cout << "An example has been made!" << endl; } Example::~Example() { cout << "An example has been destroyed!" << endl; } void main() { Example e; cout << "The program does some work" << endl; }
This code would output the following:
An example has been made! The program does some work An example has been destroyed!
Now this is getting extremely confusing. You can see the line of code that makes the example object and the place where main outputs its line, but where does An example has been destroyed! come from?
Remember, way back in the early units, you learned that declared data items have a lifetime of the block they are declared in. At the end of a block, Visual C++ looks for all the data items that are no longer required. For each data item, it looks to
its class definition and checks whether there is a destructor. If there is a destructor, it will be called.
You should never directly call a constructor or destructor yourself. If you have code that you want to happen at construction time and at some other time, extract it into a separate member function and call it from the constructor and anywhere else.
The main importance of destructors is in deallocating dynamic memory. Destructors are normally very simple pieces of code because the class will no longer exist after the destructor is called. However, sometimes you invent a class in which other pieces
of code expect the object to be present. The destructor is an opportunity to sort out objects that are linked together without relying on the programmer remembering to call the code.
A very common use of destructors is to tidy up dynamically allocated strings. In Listing 20.1, the program shows how constructors and destructors cooperate to correctly manage storage. In the program, you will also put in some
debugging code to see when these magic calls occur.
1:// File name: CONDEST.CPP 2:// Shows construction and destruction 3:// of objects and simple memory management 4:// 5:#include <iostream.h> 6:#include <string.h> 7: 8:class Name 9: { 10: public: 11: // Constructor 12: Name(); 13: // Destructor 14: ~Name(); 15: void SetName(const char * newName); 16: const char * GetName() const; 17: private: 18: char * name; 19: }; 20:void main() 21: { 22: cout << "-- First line of main() --" << endl; 23: Name firstName; 24: firstName.SetName("firstName"); 25: Name * secondName = new Name; 26: secondName->SetName("secondName"); 27: cout << "-- Before block --" << endl; 28: // New block 29: { 30: cout << "-- First line of block --" << endl; 31: Name thirdName; 32: Name fourthName; 33: thirdName.SetName("thirdName"); 34: cout << "-------------------" << endl; 35: cout << "Contents of objects" << endl; 36: cout << firstName.GetName() << endl; 37: cout << secondName->GetName() << endl; 38: cout << thirdName.GetName() << endl; 39: cout << fourthName.GetName() << endl; 40: cout << "-------------------" << endl ; 41: cout << "-- Last line of block --" << endl; 42: } // Block ends - third & fourth name destroyed 43: cout << "-- After block --" << endl; 44: delete secondName; 45: cout << "-- Last line of main() --" << endl; 46: } // firstName goes; 47://********************************************************* 48:// 49:// Name class function definitions 50:// 51: 52:// Constructor 53:Name::Name() 54: { 55: cout << "Constructor called" << endl; 56: name = 0; 57: } 58: 59:// Destructor 60:Name::~Name() 61: { 62: cout << "Destructor called "; 63: cout << "name is " << GetName() << endl; 64: delete [] name; // Delete on zero pointer is safe 65: } 66: 67:// Member function to store a name 68:// 69:void Name::SetName(const char* newName) 70: { 71: // First, remove any name that might already exist 72: // Use zero pointer to inidicate no name stored 73: // C++ will not destroy storage on a zero pointer 74: // "if (name)" 75: delete [] name; 76: // Create new storage 77: name = new char[strlen(newName) + 1]; // add 1 for 78: // terminator 79: strcpy(name,newName); // Copy data into new name 80: } 81: 82:// Member function to get the stored name 83:// Coded to always return a safe value 84:const char * Name::GetName() const 85: { 86: if (name) 87: return name; 88: else 89: return "No name exists"; 90: }
Output
-- First line of main() -- Constructor called Constructor called -- Before block -- -- First line of block -- Constructor called Constructor called ------------------- Contents of objects firstName secondName thirdName No name exists ------------------- -- Last line of block -- Destructor called name is No name exists Destructor called name is thirdName -- After block -- Destructor called name is secondName -- Last line of main() -- Destructor called name is firstName
Analysis
This skeleton program lets you see the timing of the creation and deletion of objects. By the way, there is nothing special about the timing of the class constructor and destructor. The same applies to standard C++ data types such as char or int.
The simple class Name manages a single dynamically allocated string. It allows the string to be set using SetName() (line 15) and retrieved using GetName() (line 16). The actual method of holding the string is hidden to the outside world. (When using a
class that has been created for you, you should imagine that you can't see anything marked protected and private.)
When an instance of Name is created, the constructor (line 12 and lines 53 through 57) initializes the name member to zero in line 56. Zero is an important value for a pointer. delete can be called on a zero pointer and Visual C++ knows that there is no
storage to be deleted. It is the safest value to set a pointer to when not in use. Also in line 55, the constructor outputs some text to trace when it is called.
When an instance of Name is destroyedeither by delete or by going out of scopeits destructor (lines 60 through 65) is called. The destructor only deletes the name member, remembering that name might not have been set to point to some dynamic
memory, so it should then be zero. Again, the destructor identifies when it has been called by outputting some text.
SetName(), in lines 69 through 80, allocates storage just sufficient to hold the name provided. Before it places the new name, it deletes name in case a name has already been stored. It then tests the size of the supplied string and allocates enough
memory for the string and the string terminator. Finally, it copies the input string into its newly created area of storage.
GetName() demonstrates an advantage of encapsulation. It always returns a valid string, even if it has not stored a name. In line 86, it tests name to see whether a value has been stored. If something has been stored, the value is returned. If no value
has been stored, GetName() returns an error string. This means that the class can be used, and code using it can never retrieve an invalid string. This relies on the constructor having set an initial value and the GetName() function having the extra code
in place. Furthermore, because SetString() cleverly checks the length of the string that is passed to it, it can never overwrite another variable's storage as might happen with a simple character array.
The main program simply declares and dynamically allocates some objects. Look at the output. Notice that the constructor gets called at exactly the line in the code that the declaration of the object takes place (lines 23, 25, 31, and 32). Then look at
the destructor. If delete is used, the destruction takes place at that point (line 44). If the object is locally declared (firstName, thirdName, and fourthName), the destructor takes place at the end of the block (lines 42 and 46). In the case of the main
block, the destructor call is after the last line of code (line 46). In the inner block, the destruction takes place after the last line of code of the inner block (line 42) and before the next line of code in main() (line 43).
Visual C++ allows constructors to have parameters for objects that have to be supplied with information before they can be used.
Default constructors are often used in Visual C++, but quite often it is important to be able to set an initial value. A constructor can have any parameter list that other functions have. In the case of the Name class from Listing 20.1, it is useful to
be able to set an initial string value:
class Name { public: Name(); // default constructor Name(const char * name); // char * constructor ... and so on };
In the code, there is no need to have separate lines to make and then initialize the Name variables:
Name name1("Paddy McGinty"); // Explicit construction Name name2; // Use default constructor name2.SetName("and his goat");
It is also quite all right to use default parameters:
class RetirementAge { public: RetirementAge(int age = 65); int retirementAge; }; void main() { RetirementAge ra; cout << ra.retirementAge; // prints 65 }
Recall that this is called function overloading. You can overload member functions in the same way as global functions. There is a trap to be wary of, though. If no constructor is declared for a class, Visual C++ invents one for itself (it does
nothing). If you declare a constructor yourself, Visual C++ doesn't invent a default (parameterless) constructor, as in the following example:
class A { public: int a; }; class B { public: B(int bb); int b; };
The declaration A a; would be valid. The default constructor that Visual C++ creates would be used. The declaration B b(5); is also valid, but B b; would be invalid because there is already an explicitly defined constructor for B and no default
parameterless constructor has been declared. A constructor with parameters for which all can be defaulted can be used as the default.
It is tempting to explicitly define that you want to call the default constructor by using parentheses. This is wrong because C++ thinks you are declaring a function with no parameters and a return type of the class:
A a; // calls the default constructor A a(); // Wrong! declares a parameterless function called a // with a return type A A a(1); // OK, calls constructor taking an int
Although you can create constructors with any parameter lists you like, you can never have parameters for a destructor. In other words, there are many ways of making an object, but there is always only one way of deleting an object.
Here's a final note: In Visual C++, it is useful to remember that the language designers wanted user-defined classes to be just like the built-in data types such as int and char. These built-in types have a few extra goodies, but generally the designers
succeeded. Until now, you might not have realized that in defining an int or a char, you are using the same idea of constructors and destructors that you see in classes. Here are constructors that you can call for an int and a float:
int i(5); float f(98.7F);
A special form of constructor takes another object of the class as a parameter to allow it to be copied.
A special case of constructor is called the copy constructor. This special constructor always looks like this:
ClassName(const ClassName& name)
This is a constructor that takes a reference to the class object itself as a parameter. This constructor allows an object to be copied to another object of the same type at the time of construction.
Let's look at why you would want such a thing. The easiest way to understand this is to look at another program that shows the automatic calls that C++ does. Listing 20.2 shows that Visual C++ will use a copy constructor not only
when you ask it to, but also when you do a pass by value call to a function with a class object as a parameter.
1:// File name: NAMENAME.CPP 2:// Demonstration of implicit use of constructors 3:// by Visual C++ 4: 5:#include <iostream.h> 6:#include <string.h> 7: 8:// 9:// Name - a trivial class 10:// 11:class Name 12: { 13: public: 14: // Constructors 15: Name(); // Default 16: Name(const Name& n); // Copy 17: Name(const char * newName); // Normal 18: // Destructor 19: ~Name(); 20: // Access function 21: const char * GetName() const; 22: private: 23: // Data member 24: char * name; 25: }; 26: 27:// Default constructor - ensure name is initialized 28:Name::Name() 29: { 30: name = 0; 31: cout << "Default constructor used" << endl; 32: } 33: 34:// Copy constructor - ensure string is duplicated 35:Name::Name(const Name& n) 36: { 37: if (n.name) 38: { 39: name = new char[strlen(n.name) + 1]; 40: strcpy(name,n.name); 41: } 42: else 43: name = 0; 44: cout << "Copy constructor used - " 45: << (name != 0?name : "") << endl; 46: } 47: 48:// Make a name for myself 49:Name::Name(const char * newName) 50: { 51: if (newName) 52: { 53: name = new char[strlen(newName) + 1]; 54: strcpy(name,newName); 55: } 56: else 57: name = 0; 58: cout << "const char* constructor used - " 59: << (name != 0?name : "") << endl; 60: } 61: 62:// Destructor 63:Name::~Name() 64: { 65: cout << "Destructor - " << (name != 0?name : "") << endl; 66: delete name; 67: } 68: 69:// Provide access 70:const char * Name::GetName() const 71: { 72: return name; 73: } 74: 75:// Global function with pass by value 76:void PrintName(Name n) 77: { 78: cout << "In PrintName - " << n.GetName() << endl; 79: } 80: 81:// main() function to excercise the class 82:void main() 83: { 84: cout << "-- Start of main() --" << endl; 85: Name n("Norman Lamont"); 86: 87: cout << "-- Before PrintName --" << endl; 88: PrintName(n); 89: 90: cout << "-- Before n1 declaration --" << endl; 91: Name n1(n); 92: 93: cout << "-- Before n2 declaration --" << endl; 94: Name n2; 95: 96: cout << "-- Before n2 = n1 --" << endl; 97: n2 = n1; // Unsafe !!! 98: 99: cout << "-- End of main() --" << endl; 100: }
Output
-- Start of main() -- const char* constructor used - Norman Lamont -- Before PrintName -- Copy constructor used - In PrintName - Norman Lamont Destructor - Norman Lamont -- Before n1 declaration -- Copy constructor used - Norman Lamont -- Before n2 declaration -- Default constructor used -- Before n2 = n1 -- -- End of main() -- Destructor - Norman Lamont Destructor - ¬ `5t= Lamont Destructor - Norman Lamont
Analysis
In this case, to let you follow the class more easily, all the class members are grouped at the top.
The class declares three different constructors in lines 15 to 17. The constructor in line 17 is just used to let the class have some value of interest. In line 15 the default constructor is declared, and in line 16 the const Name& parameter tells
C++ that this is a copy constructor.
The class needs a copy constructor to duplicate the class because otherwise the pointers will get mixed up (as we will see!). To properly copy a pointer member, it is necessary to duplicate the data pointed to by the pointer, or both pointers will point
to the same object. Then, at destruction time, C++ will have two separate dynamic objects owned by the pointers so that C++ will not try to delete the same dynamic storage twice.
The difficult thing to follow here is the code for the copy constructor itself, coded in lines 35 through 46. How does this function tell the two Names apart? The Name that is being made requires no qualification of its member variables. The Name that
is the copy parameter needs the parameter variable to access the class members to be copied. The name accessed in line 37 belongs to the Name to be copied. There is something fishy here! Wasn't name private? It was, but here is code accessing a private
member from outside a member function. Well, that isn't the case. A class member function has access to all the members of a class, not just all the members of the current object. The restriction is that the class object does not have any automatic means
of finding other objects of the same class, so normally the members are safe from getting mixed up.
Follow the output as you step through the main() code of lines 82 through 100. The variable n is initialized using the special const char * constructor, which means that it can be directly initialized with a character literal. The output shows that the
appropriate constructor is called. The next task is to call the global routine PrintName() in line 88. PrintName takes a single Name parameter passed by value. Recall that this means C++ must take a copy of the variable to stop the called argument from
being accidentally changed. Looking to the output, see that the copy constructor is called.
It is vital to understand that if you do not explicitly declare a copy constructor, Visual C++ will invent one for itself. It does this by copying each individual data member, but it treats a pointer as a data item and does not duplicate the data pointed to. You can stop the compiler from accidentally using the copy constructor by declaring one as a private member function (you do not have to write the body of this dummy function).
Consider what would have happened if a copy constructor had not been declared. Visual C++, trying to be helpful, would automatically try to copy the Name class, but it would then simply copy the pointer and not the data. At the end of the function (line
79), the destructor would be called and delete the character array at the end of the name pointer of the temporary variable. Unfortunately, that data also belonged to the original argument n. Fortunately, we have coded the copy constructor, so it
has copied the string itself, and the copy gets deleted. The important lesson is that the copy constructor is called by C++ itself when passing a class by value. There are other times that C++ will call it, too. To be safe with C++, you should always
declare a copy constructor when your class can't simply be copied by copying each data member individually.
Look for the output -- Before n1 declaration --. The next action is to copy the variable n into the new variable n1 in line 91. This time, the copy constructor is explicitly called. In line 94, n2 is declared. (Remember, no parentheses for the default
constructor.)
In line 97, there is a seemingly harmless line of code. Visual C++ kindly makes an assignment operator for our class. It is important to know that an assignment is not the same as a copy constructor. Look at the output. Although it seems like you should
be copying the class, the copy constructor is not used. There is a good reason that C++ does not use the copy constructor: The copy constructor is only for new class instances with no data in them. What would a constructor do with existing data owned by
the name pointer? In the next lesson, you will see how to fix this problem. Because of this error, at the end of the program (line 100) when all the destructors are called, n2 and n1 both have a pointer to the same piece of memory. n2 is deleted first, and
n1 is deleted second. The output shows some gobbledygook, but in fact you are lucky the program worked at all. It could have completely failed. Variable n survives due to n1 having been properly copied.
This is a difficult business! Don't be disheartened because you do not follow this at the moment. I hope you will at least remember that this chapter is here. When you gain more experience, you will remember that odd things happen with classes and refer
back here. It's better than being surprised later on.
Classes can contain other classes. Class data types can be used in exactly the same way as the standard Visual C++ data types.
The examples used so far have been quite simple. One of the design aims of Visual C++ was to allow programmers to define their own types that work just like the built-in data types such as int and char. In the previous example, you saw a hint that
Visual C++ even lets you make a class use operators (even though the example used the assignment operator wrongly in line 97). Before you go on to these exciting features of Visual C++, it is worth reviewing some of the simple things you can do with
classes.
Having invented the Name class, you are starting to have a useful string handling class. (I should warn you that 99.5 percent of all C++ books end up doing examples on string handling classes!) You can now easily create a string, copy a string, and set
a string to a new value with a function call. This is so useful that you could add it into the contact program you wrote earlier:
class Contact { public: Contact(); // Default Contact(const char * Name, const char * Phone, int Age); // Other functions here private: Name name; Name phoneNo; // must rename the class to something better! int age; };
Then, in the constructor, you can initialize all the variables:
Contact::Contact(const char * Name, const char * Phone, int Age) { name.SetName(Name); name.SetName(Phone); age = Age; }
Something is not quite right here. Really, you should call the constructor to make sure that the values are set up properly from the very beginning. You can't simply call the constructor, because the declaration has already been written
in the class declaration. C++ does not let you put initialization into the class declaration. Instead, there is a special way of initializing class members: the initialization list. This is simply a list of constructors called for each member that you want
to initialize. The list follows the parameters and lives just before the opening brace, with a colon (:) thrown in for good measure. This list can see all the parameters of the constructor that the list is associated with. You don't have to use parameters;
they could be set to fixed values. The initialization list finds which variable it is constructing by matching the member name.
Contact::Contact(const char * Name, const char * Phone, int Age) : name(Name),phoneNo(Phone),age(Age) { }
In this case, the initialization list was all you needed, but you still have to put in the function braces just so Visual C++ knows what it is looking at. Recall that standard data types had constructors. This shows why it is useful to know that the
standard data types also have constructors.
One of the very useful things about initialization lists is that they can be used to set constant and reference variables in a class. It is the only way that they can be set. After the constants or references have been made, they can't be assigned to.
The initialization list is in a special place (just before the constructor begins), which allows constant members to be defined before any class code can use them.
Given the following class, answer the subsequent questions:
class A { public: A(); ~A(); void Print(); } A::A() { cout << "Constructor called" << endlL } A::~A() { cout << "Destructor called" << endlL } void A::Print() { cout << "Print called " << endl; }
{ A a; a.Print(); }
{ A* a = new A; a->Print(); delete a; }
{ A* a = new A; a->Print(); }
class Telephone { Telephone(); ~Telephone(); private: char number[15]; }; void main() {
Telephone t; cout << t.number; }
class A { public: A(int a1); private: int a; }; A::A(int a1) { a = a1; } a a;
class String { public: String(); void Assign(const char * s); const char * GetString() const; private: char * string; };