Hour 11. Developing Advanced Pointers

Creating Objects on the Heap

One of the most powerful tools available to a C++ programmer is the capability to directly manipulate computer memory by using pointers.

Just as you can create a pointer to an integer, you can create a pointer to any object. If you have declared an object of type Cat, you can declare a pointer to that class and instantiate a Cat object on the heap, just as you can make one on the stack. The syntax is the same as for integers:

Cat *pCat = new Cat;

This calls the default constructor—the constructor that takes no parameters. The constructor is called whenever an object is created on the stack or on the heap.

Deleting Objects

When you call delete on a pointer to an object on the heap, the object’s destructor is called before the memory is released. This gives your class a chance to clean up, just as it does for objects destroyed on the stack.

The HeapCreator program in Listing 11.1 shows how to create and delete objects on the heap.

Listing 11.1 The Full Text of HeapCreator.cpp


 1: #include <iostream>
 2:
 3: class SimpleCat
 4: {
 5: public:
 6:     SimpleCat();
 7:     ~SimpleCat();
 8: private:
 9:     int itsAge;
10: };
11:
12: SimpleCat::SimpleCat()
13: {
14:     std::cout << "Constructor called ";
15:     itsAge = 1;
16: }
17:
18: SimpleCat::~SimpleCat()
19: {
20:     std::cout << "Destructor called ";
21: }
22:
23: int main()
24: {
25:     std::cout << "SimpleCat Frisky ... ";
26:     SimpleCat Frisky;
27:
28:     std::cout << "SimpleCat *pRags = new SimpleCat ... ";
29:     SimpleCat *pRags = new SimpleCat;
30:
31:     std::cout << "delete pRag s ... ";
32:     delete pRags;
33:
34:     std::cout << "Exiting, watch Frisky go ... ";
35:     return 0;
36: }


The program displays the following output:

SimpleCat Frisky ...
Constructor called
SimpleCat * pRags = new SimpleCat ...
Constructor called
delete pRags ...
Destructor called
Exiting, watch Frisky go ...
Destructor called

Lines 3–10 declare the stripped-down class SimpleCat. On line 26, Frisky is created on the stack, which causes the constructor to be called. On line 29, the SimpleCat pointed to by pRags is created on the heap; the constructor is called again. On line 32, delete is called on pRags, and the destructor is called. When the function ends, Frisky goes out of scope, and the destructor is called.

Accessing Data Members Using Pointers

You accessed data members and functions by using the dot operator (.) for Cat objects created locally. To access the Cat object on the heap, you must dereference the pointer and call the dot operator on the object pointed to by the pointer. Therefore, to access the GetAge member function, you write the following:

(*pRags).GetAge();

Parentheses are used to assure that pRags is dereferenced before GetAge() is accessed.

Because this is cumbersome, C++ provides a shorthand operator for indirect access: the points-to operator ->, which is created by typing a dash (-) immediately followed by the greater than symbol (>). C++ treats this as a single symbol.

The HeapAccessor program in Listing 11.2 demonstrates accessing member variables and functions of objects created on the heap.

Listing 11.2 The Full Text of HeapAccessor.cpp


 1: #include <iostream>
 2:
 3: class SimpleCat
 4: {
 5: public:
 6:     SimpleCat() { itsAge = 2; }
 7:     ~SimpleCat() {}
 8:     int GetAge() const { return itsAge; }
 9:     void SetAge(int age) { itsAge = age; }
10: private:
11:     int itsAge;
12: };
13:
14: int main()
15: {
16:     SimpleCat *Frisky = new SimpleCat;
17:     std::cout << "Frisky is " << Frisky->GetAge()
18:               << " years old" << " ";
19:
20:     Frisky->SetAge(5);
21:     std::cout << "Frisky is " << Frisky->GetAge()
22:               << " years old ";
23:
24:     delete Frisky;
25:     return 0;
26: }


HeapAccessor produces this output:

Frisky is 2 years old
Frisky is 5 years old

On line 16, a SimpleCat object is instantiated on the heap. The default constructor sets its age to 2, and the GetAge() member function is called on line 17. Because this is a pointer, the points-to operator -> is used to access the member data and functions. On line 20, the SetAge() function is called, and GetAge() is accessed again on line 21.

Member Data on the Heap

One or more of the data members of a class can be a pointer to an object on the heap. The memory can be allocated in the class constructor or in one of its functions, and it can be deleted in its destructor, as the DataMember program in Listing 11.3 illustrates.

Listing 11.3 The Full Text of DataMember.cpp


 1: #include <iostream>
 2:
 3: class SimpleCat
 4: {
 5: public:
 6:     SimpleCat();
 7:     ~SimpleCat();
 8:     int GetAge() const { return *itsAge; }
 9:     void SetAge(int age) { *itsAge = age; }
10:
11:     int GetWeight() const { return *itsWeight; }
12:     void setWeight (int weight) { *itsWeight = weight; }
13:
14: private:
15:     int *itsAge;
16:     int *itsWeight;
17: };
18:
19: SimpleCat::SimpleCat()
20: {
21:     itsAge = new int(2);
22:     itsWeight = new int(5);
23: }
24:
25: SimpleCat::~SimpleCat()
26: {
27:     delete itsAge;
28:     delete itsWeight;
29: }
30:
31: int main()
32: {
33:     SimpleCat *Frisky = new SimpleCat;
34:     std::cout << "Frisky is " << Frisky->GetAge()
35:               << " years old ";
36:
37:     Frisky->SetAge(5);
38:     std::cout << "Frisky is " << Frisky->GetAge()
39:               << " years old ";
40:
41:     delete Frisky;
42:     return 0;
43: }


This program produces the following output:

Frisky is 2 years old
Frisky is 5 years old

The class SimpleCat has two member variables—both of which are pointers to integers. The constructor (lines 19–23) initializes the pointers to memory on the heap and to the default values.

The destructor (lines 25–29) cleans up the allocated memory. Because this is the destructor, there is no point in assigning these pointers to NULL because they will no longer be accessible. This is one of the safe places to break the rule that deleted pointers should be assigned to NULL, although following the rule doesn’t hurt.

The calling function—in this case, main()—is unaware that itsAge and itsWeight are pointers to memory on the heap. main() continues to call GetAge() and SetAge() and the details of the memory management are hidden in the implementation of the class, as they should be.

When Frisky is deleted on line 41, its destructor is called. The destructor deletes each of its member pointers. If these in turn point to objects of other user-defined classes, their destructors are also called.

This is an excellent example of why to write your own destructor rather than use the compiler’s default. By default, the delete statements on lines 27 and 28 would not happen. Without those deletes, the object goes away with the delete on line 41 (including the pointers to the heap)—but not the entries on the heap itself. Without the destructor, there would be a memory leak.

The this Pointer

Every class member function has a hidden parameter: the this pointer. this points to the individual object. Therefore, in each call to GetAge() or SetAge(), the this pointer for the object is included as a hidden parameter.

The job of the this pointer is to point to the individual object whose function has been invoked. Usually, you don’t need this; you just call functions and set member variables. Occasionally, however, you need to access the object itself (perhaps to return a pointer to the current object). It is at that point that the this pointer becomes so helpful.

Normally, you don’t need to use the this pointer to access the member variables of an object from within functions of that object. You can, however, explicitly call the this pointer if you want to. The This program in Listing 11.4 illustrates how to make use of the this pointer.

Listing 11.4 The Full Text of This.cpp


 1: #include <iostream>
 2:
 3: class Rectangle
 4: {
 5: public:
 6:     Rectangle();
 7:     ~Rectangle();
 8:     void SetLength(int length) { this->itsLength = length; }
 9:     int GetLength() const { return this->itsLength; }
10:     void SetWidth(int width) { itsWidth = width; }
11:     int GetWidth() const { return itsWidth; }
12:
13: private:
14:     int itsLength;
15:     int itsWidth;
16: };
17:
18: Rectangle::Rectangle()
19: {
20:     itsWidth = 5;
21:     itsLength = 10;
22: }
23:
24: Rectangle::~Rectangle()
25: {}
26:
27: int main()
28: {
29:     Rectangle theRect;
30:     std::cout << "theRect is " << theRect.GetLength()
31:               << " feet long. ";
32:     std::cout << "theRect is " << theRect.GetWidth()
33:               << " feet wide. ";
34:
35:     theRect.SetLength(20);
36:     theRect.SetWidth(10);
37:     std::cout << "theRect is " << theRect.GetLength()
38:               << " feet long. ";
39:     std::cout << "theRect is " << theRect.GetWidth()
40:               << " feet wide. ";
41:
42:     return 0;
43: }


When you run the program, the following is displayed:

theRect is 10 feet long
theRect is 5 feet wide
theRect is 20 feet long
theRect is 10 feet wide

The SetLength() and GetLength() accessor functions explicitly use the this pointer to access the member variables of the Rectangle object. The SetWidth and GetWidth accessors do not. There is no difference in their behavior, although the function without the this pointer may be easier to read.


By the Way

If that’s all there were to the this pointer, there would be little point in bothering you with it. But because this is a pointer, it stores the memory address of an object and can be a powerful tool.

You’ll see a practical use for the this pointer later in the book, when operator overloading is discussed in Hour 15, “Using Operator Overloading.”

You don’t have to worry about creating or deleting the this pointer. The compiler takes care of that.


Stray or Dangling Pointers

A source of bugs that are nasty and difficult to find is stray pointers. A stray pointer is created when you call delete on a pointer—thereby freeing the memory that it points to—and later try to use that pointer again without reassigning it.

It is as though the Acme Mail Order company moved away and you still pressed the speed-dial button on your phone. It is possible that nothing terrible happens—a telephone rings in a deserted warehouse. Another possibility is that the telephone number has been reassigned to someone who works the night shift and you just woke them up!

Take care not to use a pointer after you have called delete on it. The pointer still points to the old area of memory, but the compiler is free to put other data there; using the pointer can cause your program to crash. Worse, your program might proceed merrily on its way and crash several minutes later. This is called a time bomb, and it is no fun. To be safe, after you delete a pointer, set it to NULL. This disarms the pointer.


By the Way

Stray pointers are often called wild pointers or dangling pointers.


const Pointers

You can use the keyword const for pointers before the type, after the type, or in both places. For example, all the following are legal declarations:

const int *pOne;
int * const pTwo;
const int * const pThree;

These three statements do not all mean the same thing. pOne is a pointer to a constant integer. The value that is pointed to can’t be changed using this pointer. That means you can’t write the following:

*pOne = 5;

If you try to do so, the compiler fails with an error.

pTwo is a constant pointer to an integer. The integer can be changed, but pTwo can’t point to anything else. A constant pointer can’t be reassigned. That means you can’t write this:

pTwo = &x;

pThree is a constant pointer to a constant integer. The value that is pointed to can’t be changed and pThree can’t be changed to point to anything else.

Draw an imaginary line just to the right of the asterisk. If the word const is to the left of the line, that means the object is constant. If the word const is to the right of the line, the pointer itself is constant:

const int *p1;   // the int pointed to is constant
int * const p2;  // p2 is constant, it can't point to anything else

const Pointers and const Member Functions

In Hour 8, “Creating Basic Classes,” you learned that you can apply the const keyword to a member function. When a function is declared as const, the compiler flags as an error any attempt to change data in the object from within that function.

If you declare a pointer to a const object, the only functions that you can call with that pointer are const functions. The ConstPointer program in Listing 11.5 illustrates this.

Listing 11.5 The Full Text of ConstPointer.cpp


 1: #include <iostream>
 2:
 3: class Rectangle
 4: {
 5: public:
 6:     Rectangle();
 7:     ~Rectangle();
 8:     void SetLength(int length) { itsLength = length; }
 9:     int GetLength() const { return itsLength; }
10:
11:     void SetWidth(int width) { itsWidth = width; }
12:     int GetWidth() const { return itsWidth; }
13:
14: private:
15:     int itsLength;
16:     int itsWidth;
17: };
18:
19: Rectangle::Rectangle():
20: itsWidth(5),
21: itsLength(10)
22: {}
23:
24: Rectangle::~Rectangle()
25: {}
26:
27: int main()
28: {
29:     Rectangle* pRect =  new Rectangle;
30:     const Rectangle *pConstRect = new Rectangle;
31:     Rectangle* const pConstPtr = new Rectangle;
32:
33:     std::cout << "pRect width: "
34:               << pRect->GetWidth() << " feet ";
35:     std::cout << "pConstRect width: "
36:               << pConstRect->GetWidth() << " feet ";
37:     std::cout << "pConstPtr width: "
38:               << pConstPtr->GetWidth() << " feet ";
39:
40:     pRect->SetWidth(10);
41:     // pConstRect->SetWidth(10);
42:     pConstPtr->SetWidth(10);
43:
44:     std::cout << "pRect width: "
45:               << pRect->GetWidth() << " feet ";
46:     std::cout << "pConstRect width: "
47:               << pConstRect->GetWidth() << " feet ";
48:     std::cout << "pConstPtr width: "
49:               << pConstPtr->GetWidth() << " feet ";
50:     return 0;
51: }


This program displays the following output:

pRect width:      5 feet
pConstRect width: 5 feet
pConstPtr width:  5 feet
pRect width:      10 feet
pConstRect width: 5 feet
pConstPtr width:  10 feet

Lines 3–17 declare Rectangle. Line 12 declares the GetWidth() member function const. Line 29 declares a pointer to a Rectangle. Line 30 declares pConstRect, which is a pointer to a constant Rectangle. Line 31 declares pConstPtr, which is a constant pointer to Rectangle.

Lines 33–38 display the value of the widths.

In line 40, pRect is used to set the width of the rectangle to 10. In line 41, pConstRect would be used, but it was declared to point to a constant Rectangle. Therefore, it cannot legally call a non-const member function and is commented out. On line 31, pConstPtr is declared to be a constant pointer to a rectangle. In other words, the pointer is constant and cannot point to anything else, but the rectangle is not constant.


By the Way

When you declare an object to be const, you are, in effect, declaring that the this pointer is a pointer to a const object. A const this pointer can be used only with const member functions.

Constant objects and constant pointers are discussed again in the next hour, when references to constant objects are discussed.


Summary

Pointers can be created to point to simple data types like integers and to objects as well.

Objects can be created and deleted on the heap. If you have declared an object, you can declare a pointer to that class and instantiate the object on the heap.

Data members of a class can be pointers to objects on the heap. Memory can be allocated in the class constructor or one of its functions and deleted in the destructor.

The ability to directly access computer memory by using pointers is one of the most powerful tools available to a C++ programmer.

Q&A

Q. Why should I declare an object as const if it limits what I can do with it?

A. As a programmer, you want to enlist the compiler in helping you find bugs. One serious bug that is difficult to find is a function that changes an object in ways that aren’t obvious to the calling function. Declaring an object const prevents such changes.

Q. Java doesn’t have pointers. Why do I need them in C++?

A. For most tasks, you don’t. You can achieve the same results using other techniques. Java creator James Gosling felt that pointers were too error-prone for programmers, so he left that feature out of his language. Pointers in C++ only are completely necessary when working directly with hardware such as on a device driver. But once you master pointers, you’ll find you can do a lot with their power.

Q. Why do people have red eyes in photographs?

A. The camera’s bright flash reflects off the retinas, revealing the red color of blood vessels that nourish the eyes and giving pupils a demonic appearance.

Some cameras offer a “red eye reduction” feature that reduces this effect by firing two flashes—one before the picture is taken. The first flash causes the photo subject’s pupils to contract, minimizing the red-eye effect.

Pupils that appear white in a photo are a possible indicator of eye disease such as retinoblastoma, a highly treatable cancer if detected early. If a photo shows a white-eye effect, the subject should see an ophthalmologist to ensure the eyes are healthy.

Workshop

We spent the past hour advancing your knowledge of pointers, and it is now time for you to answer a few questions and complete a couple of exercises to firm up that knowledge.

Quiz

1. What keywords are used to allocate and release space from the heap in C++?

A. alloc and dealloc

B. public and private

C. new and delete

2. When is an object deleted (the destructor called) if you do not issue the delete yourself?

A. When the program ends

B. When the object’s scope ends

C. Never

3. What is a pointer called that is used after a delete was performed on that pointer?

A. A null pointer

B. A stray pointer

C. A zero pointer

Answers

1. C. new is used to allocate space on the heap and delete is used to release it.

2. B. When the scope for an object is exited, that object is automatically deleted. If an object is created in main() and not deleted by the programmer, when main() is exited, the destructor is called. The output from Listing 11.1 shows this happening.

3. B. A stray pointer. You don’t really know what that memory location is being used for!

Activities

1. Add a cat named Spooky to the HeapCreator program.

2. Modify the HeapAccessor program so that it does not use the points-to operator.

To see solutions to these activities, visit this book’s website at http://cplusplus.cadenhead.org.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.207.206