|

Up
to now, you’ve been setting the member variables
of objects in the body of the constructor. Constructors,
however, are invoked in two stages: the initialization
stage and the body.
Most
variables can be set in either stage, either by
initializing in the initialization stage or by assigning
in the body of the constructor. It is cleaner, and
often more efficient, to initialize member variables
at the initialization stage. The following example
shows how to initialize member variables:
CAT():
// constructor name and parameters
itsAge(5),
// initialization list
itsWeight(8)
{
} // body of constructor
After
the closing parentheses on the constructor’s parameter
list, write a colon. Then write the name of the
member variable and a pair of parentheses. Inside
the parentheses, write the expression to be used
to initialize that member variable. If there is
more than one initialization, separate each one
with a comma. Listing 10.4 shows the definition
of the constructors from Listing 10.3, with initialization
of the member variables rather than assignment.
Listing
10.4. A code snippet showing initialization
of member variables.
1:
Rectangle::Rectangle():
2:
itsWidth(5),
3:
itsLength(10)
4:
{
5:
};
6:
7:
Rectangle::Rectangle (int width, int length)
8:
itsWidth(width),
9:
itsLength(length)
10:
11:
};
OUTPUT:
No output.
There
are some variables that must be initialized and
cannot be assigned to: references and constants.
It is common to have other assignments or action
statements in the body of the constructor; however,
it is best to use initialization as much as possible.
The
Copy Constructor
In
addition to providing a default constructor and
destructor, the compiler provides a default copy
constructor. The copy constructor is called every
time a copy of an object is made.
When
you pass an object by value, either into a function
or as a function’s return value, a temporary copy
of that object is made. If the object is a user-defined
object, the class’s copy constructor is called,
as you saw in last unit Listing 9.6.
All
copy constructors take one parameter, a reference
to an object of the same class. It is a good idea
to make it a constant reference, because the constructor
will not have to alter the object passed in. For
example:
CAT(const
CAT & theCat);
Here
the CAT constructor takes a constant reference to
an existing CAT object. The goal of the copy constructor
is to make a copy of theCAT.
The
default copy constructor simply copies each member
variable from the object passed as a parameter to
the member variables of the new object. This is
called a member-wise (or shallow) copy, and although
this is fine for most member variables, it breaks
pretty quickly for member variables that are pointers
to objects on the free store.
New Term: A shallow or member-wise
copy copies the exact values of one object’s member
variables into another object. Pointers in both
objects end up pointing to the same memory. A deep
copy copies the values allocated on the heap to
newly allocated memory.
If
the CAT class includes a member variable, itsAge,
that points to an integer on the free store, the
default copy constructor will copy the passed-in
CAT’s itsAge member variable to the new CAT’s itsAge
member variable. The two objects will now point
to the same memory, as illustrated in Figure 10.1.

Figure 10.1.Using the default copy
constructor.
This
will lead to a disaster when either CAT goes out
of scope. As mentioned in Unit 8, "Pointers," the
job of the destructor is to clean up this memory.
If the original CAT’s destructor frees this memory
and the new CAT is still pointing to the memory,
a stray pointer has been created, and the program
is in mortal danger. Figure 10.2 illustrates this
problem.

Figure 10.2. Creating a stray pointer.
The
solution to this is to create your own copy constructor
and to allocate the memory as required. Once the
memory is allocated, the old values can be copied
into the new memory. This is called a deep copy.
Listing 10.5 illustrates how to do this.
Listing
10.5. Copy constructors.
1:
// Listing 10.5
2:
// Copy constructors
3:
4:
#include <iostream.h>
5:
6:
class CAT
7:
{
8:
public:
9:
CAT(); // default constructor
10:
CAT (const CAT &); // copy constructor
11:
~CAT(); // destructor
12:
int GetAge() const { return *itsAge; }
13:
int GetWeight() const { return *itsWeight; }
14:
void SetAge(int age) { *itsAge = age; }
15:
16:
private:
17:
int *itsAge;
18:
int *itsWeight;
19:
};
20:
21:
CAT::CAT()
22:
{
23:
itsAge = new int;
24:
itsWeight = new int;
25:
*itsAge = 5;
26:
*itsWeight = 9;
27:
}
28:
29:
CAT::CAT(const CAT & rhs)
30:
{
31:
itsAge = new int;
32:
itsWeight = new int;
33:
*itsAge = rhs.GetAge();
34:
*itsWeight = rhs.GetWeight();
35:
}
36:
37:
CAT::~CAT()
38:
{
39:
delete itsAge;
40:
itsAge = 0;
41:
delete itsWeight;
42:
itsWeight = 0;
43:
}
44:
45:
int main()
46:
{
47:
CAT frisky;
48:
cout << "frisky’s age: " << frisky.GetAge()
<< endl;
49:
cout << "Setting frisky to 6...\n";
50:
frisky.SetAge(6);
51:
cout << "Creating boots from frisky\n";
52:
CAT boots(frisky);
53:
cout << "frisky’s age: " << frisky.GetAge()
<< endl;
54:
cout << "boots’ age: " << boots.GetAge()
<< endl;
55:
cout << "setting frisky to 7...\n";
56:
frisky.SetAge(7);
57:
cout << "frisky’s age: " << frisky.GetAge()
<< endl;
58:
cout << "boot’s age: " << boots.GetAge()
<< endl;
59:
return 0;
60:
}
OUTPUT:
frisky’s age: 5
Setting frisky to 6...
Creating boots from frisky
frisky’s age: 6
boots’ age: 6
setting frisky to 7...
frisky’s age: 7
boots’ age: 6
ANALYSIS: On lines 6-19, the CAT class
is declared. Note that on line 9 a default constructor
is declared, and on line 10 a copy constructor is
declared. On lines 17 and 18, two member variables
are declared, each as a pointer to an integer. Typically
there’d be little reason for a class to store int
member variables as pointers, but this was done
to illustrate how to manage member variables on
the free store.
The
default constructor, on lines 21-27, allocates room
on the free store for two int variables and then
assigns values to them.
The
copy constructor begins on line 29. Note that the
parameter is rhs. It is common to refer to the parameter
to a copy constructor as rhs, which stands for right-hand
side. When you look at the assignments in lines
33 and 34, you’ll see that the object passed in
as a parameter is on the right-hand side of the
equals sign. Here’s how it works.
On
lines 31 and 32, memory is allocated on the free
store. Then, on lines 33 and 34, the value at the
new memory location is assigned the values from
the existing CAT.
The
parameter rhs is a CAT that is passed into the copy
constructor as a constant reference. The member
function rhs.GetAge() returns the value stored in
the memory pointed to by rhs’s member variable itsAge.
As a CAT object, rhs has all the member variables
of any other CAT.
When
the copy constructor is called to create a new CAT,
an existing CAT is passed in as a parameter. The
new CAT can refer to its own member variables directly;
however, it must access rhs’s member variables using
the public accessor methods.
Figure
10.3 diagrams what is happening here. The values
pointed to by the existing CAT are copied to the
memory allocated for the new CAT

Figure 10.3. Deep copy illustrated.
On
line 47, a CAT called frisky is created. frisky’s
age is printed, and then his age is set to 6 on
line 50. On line 52, a new CAT boots is created,
using the copy constructor and passing in frisky.
Had frisky been passed as a parameter to a function,
this same call to the copy constructor would have
been made by the compiler.
On
lines 53 and 54, the ages of both CATs are printed.
Sure enough, boots has frisky’s age, 6, not the
default age of 5. On line 56, frisky’s age is set
to 7, and then the ages are printed again. This
time frisky’s age is 7, but boots’ age is still
6, demonstrating that they are stored in separate
areas of memory.
When
the CATs fall out of scope, their destructors are
automatically invoked. The implementation of the
CAT destructor is shown on lines 37-43. delete is
called on both pointers, itsAge and itsWeight, returning
the allocated memory to the free store. Also, for
safety, the pointers are reassigned to NULL.
Operator
Overloading
C++
has a number of built-in types, including int, real,
char, and so forth. Each of these has a number of
built-in operators, such as addition (+) and multiplication
(*). C++ enables you to add these operators to your
own classes as well.
In
order to explore operator overloading fully, Listing
10.6 creates a new class, Counter. A Counter object
will be used in counting (surprise!) in loops and
other applications where a number must be incremented,
decremented, or otherwise tracked.
Listing
10.6. The Counter class.
1:
// Listing 10.6
2:
// The Counter class
3:
4:
typedef unsigned short USHORT;
5:
#include <iostream.h>
6:
7:
class Counter
8:
{
9:
public:
10:
Counter();
11:
~Counter(){}
12:
USHORT GetItsVal()const { return itsVal; }
13:
void SetItsVal(USHORT x) {itsVal = x; }
14:
15:
private:
16:
USHORT itsVal;
17:
18:
};
19:
20:
Counter::Counter():
21:
itsVal(0)
22:
{};
23:
24:
int main()
25:
{
26:
Counter i;
27:
cout << "The value of i is " << i.GetItsVal()
<< endl;
28:
return 0;
29:
}
OUTPUT:
The value of i is 0
ANALYSIS: As it stands, this is a pretty
useless class. It is defined on lines 7-18. Its
only member variable is a USHORT. The default constructor,
which is declared on line 10 and whose implementation
is on line 20, initializes the one member variable,
itsVal, to zero.
Unlike
an honest, red-blooded USHORT, the Counter object
cannot be incremented, decremented, added, assigned,
or otherwise manipulated. In exchange for this,
it makes printing its value far more difficult!
Writing
an Increment Function
Operator
overloading restores much of the functionality that
has been stripped out of this class. For example,
there are two ways to add the ability to increment
a Counter object. The first is to write an increment
method, as shown in Listing 10.7.
Listing
10.7. Adding an increment operator.
1:
// Listing 10.7
2:
// The Counter class
3:
4:
typedef unsigned short USHORT;
5:
#include <iostream.h>
6:
7:
class Counter
8:
{
9:
public:
10:
Counter();
11:
~Counter(){}
12:
USHORT GetItsVal()const { return itsVal; }
13:
void SetItsVal(USHORT x) {itsVal = x; }
14:
void Increment() { ++itsVal; }
15:
16:
private:
17:
USHORT itsVal;
18:
19:
};
20:
21:
Counter::Counter():
22:
itsVal(0)
23:
{};
24:
25:
int main()
26:
{
27:
Counter i;
28:
cout << "The value of i is " << i.GetItsVal()
<< endl;
29:
i.Increment();
30:
cout << "The value of i is " << i.GetItsVal()
<< endl;
31:
return 0;
32:
}
OUTPUT:
The value of i is 0
The value of i is 1
ANALYSIS: Listing 10.7 adds an Increment
function, defined on line 14. Although this works,
it is cumbersome to use. The program cries out for
the ability to add a ++ operator, and of course
this can be done.
Overloading
the Prefix Operator
Prefix
operators can be overloaded by declaring functions
with the form:
|
returnType
Operator op (parameters)
|
Here,
op is the operator to overload. Thus, the ++ operator
can be overloaded with the following syntax:
Listing
10.8 demonstrates this alternative.
Listing
10.8. Overloading operator++.
1:
// Listing 10.8
2:
// The Counter class
3:
4:
typedef unsigned short USHORT;
5:
#include <iostream.h>
6:
7:
class Counter
8:
{
9:
public:
10:
Counter();
11:
~Counter(){}
12:
USHORT GetItsVal()const { return itsVal; }
13:
void SetItsVal(USHORT x) {itsVal = x; }
14:
void Increment() { ++itsVal; }
15:
void operator++ () { ++itsVal; }
16:
17:
private:
18:
USHORT itsVal;
19:
20:
};
21:
22:
Counter::Counter():
23:
itsVal(0)
24:
{};
25:
26:
int main()
27:
{
28:
Counter i;
29:
cout << "The value of i is " << i.GetItsVal()
<< endl;
30:
i.Increment();
31:
cout << "The value of i is " << i.GetItsVal()
<< endl;
32:
++i;
33:
cout << "The value of i is " << i.GetItsVal()
<< endl;
34:
return 0;
35:
}
OUTPUT:
The
value of i is 0
The
value of i is 1
The
value of i is 2
Analysis: On line 15, operator++ is
overloaded, and it’s used on line 32. This is far
closer to the syntax one would expect with the Counter
object. At this point, you might consider putting
in the extra abilities for which Counter was created
in the first place, such as detecting when the Counter
overruns its maximum size. There is a significant
defect in the way the increment operator was written,
however. If you want to put the Counter on the right
side of an assignment, it will fail. For example:
Counter
a = ++i;
This
code intends to create a new Counter, a, and then
assign to it the value in i after i is incremented.
The built-in copy constructor will handle the assignment,
but the current increment operator does not return
a Counter object. It returns void. You can’t assign
a void object to a Counter object. (You can’t make
something from nothing!)
Returning
Types in Overloaded Operator Functions
Clearly,
what you want is to return a Counter object so that
it can be assigned to another Counter object. Which
object should be returned? One approach would be
to create a temporary object and return that. Listing
10.9 illustrates this approach.
Listing
10.9. Returning a temporary object.
1:
// Listing 10.9
2:
// operator++ returns a temporary object
3:
4:
typedef unsigned short USHORT;
5:
#include <iostream.h>
6:
7:
class Counter
8:
{
9:
public:
10:
Counter();
11:
~Counter(){}
12:
USHORT GetItsVal()const { return itsVal; }
13:
void SetItsVal(USHORT x) {itsVal = x; }
14:
void Increment() { ++itsVal; }
15:
Counter operator++ ();
16:
17:
private:
18:
USHORT itsVal;
19:
20:
};
21:
22:
Counter::Counter():
23:
itsVal(0)
24:
{};
25:
26:
Counter Counter::operator++()
27:
{
28:
++itsVal;
29:
Counter temp;
30:
temp.SetItsVal(itsVal);
31:
return temp;
32:
}
33:
34:
int main()
35:
{
36:
Counter i;
37:
cout << "The value of i is " << i.GetItsVal()
<< endl;
38:
i.Increment();
39:
cout << "The value of i is " << i.GetItsVal()
<< endl;
40:
++i;
41:
cout << "The value of i is " << i.GetItsVal()
<< endl;
42:
Counter a = ++i;
43:
cout << "The value of a: " << a.GetItsVal();
44:
cout << " and i: " << i.GetItsVal()
<< endl;
45:
return 0;
46:
}
OUTPUT:
The value of i is 0
The
value of i is 1
The
value of i is 2
The
value of a: 3 and i: 3
Analysis: In this version, operator++
has been declared on line 15 to return a Counter
object. On line 29, a temporary variable, temp,
is created and its value is set to match that in
the current object. That temporary variable is returned
and immediately assigned to a on line 42.
Returning
Nameless Temporaries
There
is really no need to name the temporary object created
on line 29. If Counter had a constructor that took
a value, you could simply return the result of that
constructor as the return value of the increment
operator. Listing 10.10 illustrates this idea.
Listing
10.10. Returning a nameless temporary object.
1:
// Listing 10.10
2:
// operator++ returns a nameless temporary object
3:
4:
typedef unsigned short USHORT;
5:
#include <iostream.h>
6:
7:
class Counter
8:
{
9:
public:
10:
Counter();
11:
Counter(USHORT val);
12:
~Counter(){}
13:
USHORT GetItsVal()const { return itsVal; }
14:
void SetItsVal(USHORT x) {itsVal = x; }
15:
void Increment() { ++itsVal; }
16:
Counter operator++ ();
17:
18:
private:
19:
USHORT itsVal;
20:
21:
};
22:
23:
Counter::Counter():
24:
itsVal(0)
25:
{}
26:
27:
Counter::Counter(USHORT val):
28:
itsVal(val)
29:
{}
30:
31:
Counter Counter::operator++()
32:
{
33:
++itsVal;
34:
return Counter (itsVal);
35:
}
36:
37:
int main()
38:
{
39:
Counter i;
40:
cout << "The value of i is " << i.GetItsVal()
<< endl;
41:
i.Increment();
42:
cout << "The value of i is " << i.GetItsVal()
<< endl;
43:
++i;
44:
cout << "The value of i is " << i.GetItsVal()
<< endl;
45:
Counter a = ++i;
46:
cout << "The value of a: " << a.GetItsVal();
47:
cout << " and i: " << i.GetItsVal()
<< endl;
48:
return 0;
49:
}
OUTPUT:
The
value of i is 0
The
value of i is 1
The
value of i is 2
The
value of a: 3 and i: 3
Analysis: On line 11, a new constructor
is declared that takes a USHORT. The implementation
is on lines 27-29. It initializes itsVal with the
passed-in value. The implementation of operator++
is now simplified. On line 33, itsVal is incremented.
Then on line 34, a temporary Counter object is created,
initialized to the value in itsVal, and then returned
as the result of the operator++.
This
is more elegant, but begs the question, "Why create
a temporary object at all?" Remember that each temporary
object must be constructed and later destroyed—each
one potentially an expensive operation. Also, the
object i already exists and already has the right
value, so why not return it? We’ll solve this problem
by using the this pointer.
Using
the this Pointer
The
this pointer, as discussed in last unit, was passed
to the operator++ member function as to all member
functions. The this pointer points to i, and if
it’s dereferenced it will return the object i, which
already has the right value in its member variable
itsVal. Listing 10.11 illustrates returning the
dereferenced this pointer and avoiding the creation
of an unneeded temporary object.
Listing
10.11. Returning the this pointer.
1:
// Listing 10.11
2:
// Returning the dereferenced this pointer
3:
4:
typedef unsigned short USHORT;
5:
#include <iostream.h>
6:
7:
class Counter
8:
{
9:
public:
10:
Counter();
11:
~Counter(){}
12:
USHORT GetItsVal()const { return itsVal; }
13:
void SetItsVal(USHORT x) {itsVal = x; }
14:
void Increment() { ++itsVal; }
15:
const Counter& operator++ ();
16:
17:
private:
18:
USHORT itsVal;
19:
20:
};
21:
22:
Counter::Counter():
23:
itsVal(0)
24:
{};
25:
26:
const Counter& Counter::operator++()
27:
{
28:
++itsVal;
29:
return *this;
30:
}
31:
32:
int main()
33:
{
34:
Counter i;
35:
cout << "The value of i is " << i.GetItsVal()
<< endl;
36:
i.Increment();
37:
cout << "The value of i is " << i.GetItsVal()
<< endl;
38:
++i;
39:
cout << "The value of i is " << i.GetItsVal()
<< endl;
40:
Counter a = ++i;
41:
cout << "The value of a: " << a.GetItsVal();
42:
cout << " and i: " << i.GetItsVal()
<< endl;
48:
return 0;
49:
}
OUTPUT:
The value of i is 0
The value of i is 1
The value of i is 2
The value of a: 3 and i: 3
ANALYSIS: The implementation of operator++,
on lines 26-30, has been changed to dereference
the this pointer and to return the current object.
This provides a Counter object to be assigned to
a. As discussed above, if the Counter object allocated
memory, it would be important to override the copy
constructor. In this case, the default copy constructor
works fine.
Note
that the value returned is a Counter reference,
thereby avoiding the creation of an extra temporary
object. It is a const reference because the value
should not be changed by the function using this
Counter.
Overloading
the Postfix Operator
So
far you’ve overloaded the prefix operator. What
if you want to overload the postfix increment operator?
Here the compiler has a problem: How is it to differentiate
between prefix and postfix? By convention, an integer
variable is supplied as a parameter to the operator
declaration. The parameter’s value is ignored; it
is just a signal that this is the postfix operator.
Difference
Between Prefix and Postfix
Before
we can write the postfix operator, we must understand
how it is different from the prefix operator. We
reviewed this in detail in Unit 4, "Expressions
and Statements" (see Listing 4.3).
To
review, prefix says "increment, and then fetch,"
while postfix says "fetch, and then increment."
Thus,
while the prefix operator can simply increment the
value and then return the object itself, the postfix
must return the value that existed before it was
incremented. To do this, we must create a temporary
object that will hold the original value, then increment
the value of the original object, and then return
the temporary.
Let’s
go over that again. Consider the following line
of code:
a
= x++;
If
x was 5, after this statement a is 5, but x is 6.
Thus, we returned the value in x and assigned it
to a, and then we increased the value of x. If x
is an object, its postfix increment operator must
stash away the original value (5) in a temporary
object, increment x’s value to 6, and then return
that temporary to assign its value to a.
Note
that since we are returning the temporary, we must
return it by value and not by reference, as the
temporary will go out of scope as soon as the function
returns.
Listing
10.12 demonstrates the use of both the prefix and
the postfix operators.
Listing
10.12. Prefix and postfix operators.
1:
// Listing 10.12
2:
// Returning the dereferenced this pointer
3:
4:
typedef unsigned short USHORT;
5:
#include <iostream.h>
6:
7:
class Counter
8:
{
9:
public:
10:
Counter();
11:
~Counter(){}
12:
USHORT GetItsVal()const { return itsVal; }
13:
void SetItsVal(USHORT x) {itsVal = x; }
14:
const Counter& operator++ (); // prefix
15:
const Counter operator++ (int); // postfix
16:
17:
private:
18:
USHORT itsVal;
19:
};
20:
21:
Counter::Counter():
22:
itsVal(0)
23:
{}
24:
25:
const Counter& Counter::operator++()
26:
{
27:
++itsVal;
28:
return *this;
29:
}
30:
31:
const Counter Counter::operator++(int)
32:
{
33:
Counter temp(*this);
34:
++itsVal;
35:
return temp;
36:
}
37:
38:
int main()
39:
{
40:
Counter i;
41:
cout << "The value of i is " << i.GetItsVal()
<< endl;
42:
i++;
43:
cout << "The value of i is " << i.GetItsVal()
<< endl;
44:
++i;
45:
cout << "The value of i is " << i.GetItsVal()
<< endl;
46:
Counter a = ++i;
47:
cout << "The value of a: " << a.GetItsVal();
48:
cout << " and i: " << i.GetItsVal()
<< endl;
49:
a = i++;
50:
cout << "The value of a: " << a.GetItsVal();
51:
cout << " and i: " << i.GetItsVal()
<< endl;
52:
return 0;
53:
}
OUTPUT:
The value of i is 0
The value of i is 1
The value of i is 2
The value of a: 3 and i: 3
The value of a: 3 and i: 4
ANALYSIS: The postfix operator is declared
on line 15 and implemented on lines 31-36. Note
that the call to the prefix operator on line 14
does not include the flag integer (x), but is used
with its normal syntax. The postfix operator uses
a flag value (x) to signal that it is the postfix
and not the prefix. The flag value (x) is never
used, however.
|