C++

Initializing Objects

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.

10.1.JPG (6862 bytes)
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.

10.2.JPG (6862 bytes)
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

10.3.JPG (8101 bytes)
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:

void operator++ ()

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.

Back to Index