Day 3
Week1
68 Day 3
Pointers: Welcome to My Nightmare
Pointers are one of the most confusing aspects of the C++ language. They are also one of the
most powerful features of C++. My goal in this section is not to teach you the textbook
definition of pointers, but rather to teach you pointers in the context of how you will use them
in your C++Builder programs. So what is a pointer? It’s a variable that holds the address of
another variable. There, that wasn’t so bad, was it? I wish it were that simple! Because a pointer
holds the address of another variable, it is said to “point to” the second variable. This is called
indirection because the pointer does not have a direct association with the actual data, but
rather an indirect association.
A pointer is a variable that holds the address of another variable.
Because the pointer does not have a direct association with the actual data,
indirection is the term used when referring to this indirect association.
Let’s look at an example. Earlier we talked about arrays. Let’s say that you had an array of ints.
You could access the individual elements of the array using the subscript operator, as I talked
about on Day 1, “Getting Your Feet Wet”:
int array[] = { 5, 10, 15, 20, 25 };
int someVariable = array[3]; // the value 20
You could also use a pointer to accomplish the same thing:
int array[] = { 5, 10, 15, 20, 25 };
int* ptr = array;
int someVariable = ptr[3];
In this example, the memory location of the beginning of the array is assigned to the pointer
named ptr. Note that the pointer is a pointer of the data type int and that the indirection
operator (the * symbol) is used when you declare a pointer. You can declare a pointer to any
of the integral data types (int, char, long, short, and so on), as well as a pointer to objects
(structures or classes). After the assignment the pointer contains the memory address of the
start of the array, and as such points to the array.
The name of an array variable, when used without the subscript
operator, returns the memory address of the first element of the array.
Put another way, the variable name of an array is a pointer, to the start
of the array. That makes it possible to assign an array to a pointer, as in
the preceding example.
NEW TERM
NOTE
NEW TERM
Up to Your Neck in C++ 69
3
In this case you can now use the pointer, ptr, just as you would the array name itself. I can
hear you wondering, though: “But why would you want to?” The truth is that in this example
there is no real benefit to using a pointer. The real benefit of pointers is when it comes to
creating objects dynamically in memory. In that case, a pointer is necessary to access the
object. I really can’t go on with this discussion, though, until I digress a moment and talk
about the two ways you can create variables and objects.
Local Versus Dynamic Memory Usage
So far all my sample programs have used local allocation of objects—that is, the memory
required for a variable or object is obtained from the program’s stack.
Local allocation means that the memory required for a variable or object is obtained
from the program’s stack.
The stack is an area of working memory set aside by the program when the program
starts.
Any memory the program needs for things such as local variables, function calls, and so on
is taken from the stack. This memory is allocated as needed and then freed when it is no longer
needed. Usually this happens when the program enters a function or other local code block.
Memory for any local variables the function uses is allocated when the function is entered.
When the function returns, all of the memory allocated for the function’s use is freed. It all
happens for you automatically; you don’t have to give any thought to how or if the memory
is freed.
Local allocation has its good points and its bad points. On the plus side, memory can be
allocated from the stack very quickly. The downside is that the stack is of a fixed size and
cannot be changed as the program runs. If your program runs out of stack space, weird things
start to happen. Your program might just crash, it might start behaving oddly, or it might
seem to perform normally but crash when the program terminates. This is less of a problem
in the 32-bit world than it is in 16-bit programming, but it’s still a consideration.
For things like variables of the built-in data types and small arrays, there is no point in doing
anything other than local allocation. But if you are going to be using large arrays, structures,
or classes, you will probably want to use dynamic allocation from the heap. This amounts to
your free physical RAM plus all of your free hard disk space. In other words, you could easily
have 100MB of heap memory available on a typical Windows system. The good news here
is that you have virtually unlimited memory available for your programs. The bad news is that
memory allocated dynamically requires some additional overhead, and as such is just a
smidgen slower than memory allocated from the stack. In most programs the extra overhead
is not noticed in the least. An additional drawback of dynamic allocation is that it requires
more from the programmer. Not a lot more, mind you, but a little.
NEW TERM
NEW TERM
70 Day 3
Dynamic allocation means that memory required for an object is allocated from the
heap.
The heap in a Windows program refers to all of your computer’s virtual memory.
Dynamic Allocation and Pointers
In a C++ program, memory is allocated dynamically by using the new operator.
I’m going to talk about new a little later in the chapter, but you need a little sampler as I
continue the discussion about pointers. Earlier I talked about structures and used the
mailingListRecord structure as an example. Allocating a structure from the stack looks like
this:
mailingListRecord listArray;
strcpy(listArray.firstName, “Ian”);
strcpy(listArray.lastName, “Spencer”);
// etc.
That’s what I did earlier when I talked about structures. Now I’ll create the array dynamically
rather than locally:
mailingListRecord* listArray;
listArray = new mailingListRecord;
strcpy(listArray->firstName, “Ian”);
strcpy(listArray->lastName, “Spencer”);
// etc.
The first line declares a pointer to a mailingListRecord structure. The next line initializes the
pointer by creating a new instance of a mailingListRecord structure dynamically. This is the
process by which you dynamically create and access objects in C++.
And Now Back to Our Program
Now you begin to see where pointers fit into the scheme of things. When you create an object
dynamically, the new operator returns a pointer to the object in memory. You need that
pointer to be able to do anything with the object. Figure 3.1 illustrates how the pointer points
to the object in memory. Note that although the memory for the dynamically created object
is allocated from heap memory, the actual pointer is a local variable and is allocated from the
stack.
NEW TERM
NEW TERM
NEW TERM
Up to Your Neck in C++ 71
Let’s go back to a code snippet you saw earlier: 3
mailingListRecord* listArray;
listArray = new mailingListRecord;
strcpy(listArray->firstName, “Ian”);
strcpy(listArray->lastName, “Spencer”);
// etc.
On the third line you see that the firstName data member of the structure is accessed using
the indirect member operator (->) rather than the structure member operator. (We discussed
the structure member operator yesterday in the section titled “Structures.” The term direct
member operator is also used and is more representative than structure member operator, so
I will use direct member operator from now on.) When you create an object dynamically, you
must access the object’s data members and functions using this operator.
Creating an array of structures dynamically requires a bit more work. Again, here’s the stackbased
version:
mailingListRecord listArray[3];
listArray[0].zip = 57441;
And the dynamic version:
mailingListRecord* listArray[3];
for (int i=0;i<3;i++)
listArray[i] = new mailingListrecord;
listArray[0]->zip = 57441;
Note that I have to create a new instance of the structure for each element of the array. Notice
also that to access a data member of the array, I use the indirect membership operator
combined with the subscript operator.
Figure 3.1.
A pointer to an object
in memory. mailingListRecord*
listArray
stack memory
listArray
listArray points to
address 0x00780E50,
which is an instance
of the mailingListRecord
structure in memory
mailingListRecord
structure in memory
heap memory
0x00780E50
firstName
lastName
address
city
state
zip
72 Day 3
Uninitialized pointers contain random values just like any other
uninitialized variable. Attempting to use an uninitialized pointer can
wreak havoc on a program. In many cases, a pointer is declared and
immediately initialized:
MyArray* array = new MyArray;
Sometimes, however, you will declare a pointer and then not initialize
it until sometime later in the program. If you attempt to use the
pointer before initializing it, the pointer will point to some random
memory location, and modifying that memory could cause all sorts of
nasty problems. Often the problems caused by modifying unknown
memory do not show up immediately, making the bug appear to be
random. To be safe, you should initialize a pointer to 0 when you
declare it:
MyArray* array = 0;
If you attempt to use a NULL pointer (any pointer set to NULL or 0), you
will immediately get an access violation or GPF from Windows.
Although this may not sound like a good thing, it is certainly the lesser
of two evils. It is far better to have an immediate error at the point of
the infraction than to have a random problem that may show up
further down the road.
Dereferencing a Pointer
Frequently you will need to dereference a pointer in order to retrieve the contents of the
memory location (the object) that a pointer points to. Take the following example:
int x = 20;
int* ptrx = &x;
// later...
int z = *ptrx;
I can just imagine your frustration right now. What a mess! Take heart; it’s not quite as bad
as it might appear. The first line in this example declares an int variable called x and assigns
it a value of 20. The next line declares a pointer to an int and assigns to the pointer the address
of the variable x. This is done by using the address-of operator (&). In this example, the addressof
operator tells the compiler, “Give me the address of the variable x, not the value of x itself.”
After the assignment, ptrx contains the memory address of x. Later on in the program you
might need to get the value of the object pointed to by ptrx. You might think to try this:
int z = ptrx; // wrong!
WARNING
Up to Your Neck in C++ 73
3
That won’t work, however, because you are trying to assign a memory address to a regular
variable. When you try to compile this line, the compiler will spit back an error stating, Cannot
convert int* to int. That makes sense because you are dealing with two different types of
variables. So you need to dereference the pointer using the indirection operator:
int z = *ptrx;
This could be considered the opposite of the address-of operator. Here you don’t want the
actual value of ptrx because the actual value is a memory address. Instead you want the value
of the object pointed to by that memory address. So, in this case, the indirection operator tells
the compiler, “Give me the value of the object ptrx points to, not the actual value of ptrx.”
Dereferencing a pointer means to retrieve the contents of the memory location (the
object) that a pointer points to.
As you can see, the indirection operator is used to declare a pointer
(int* x;) and also to dereference a pointer (int z = *x;). The compiler
can tell from the context in which the indirection operator is used
what to do in each case. You don’t have to worry that the compiler
won’t know what you intend.
C++ syntax is largely a personal thing. I prefer to use the indirection
operator next to the data type when declaring a pointer, and next to the
pointer when dereferencing a pointer:
int* x;
SomeClass* aClass = new SomeClass;
char* s = new char[256];
int z = *x;
SomeClass temp = *aClass;
Others prefer to place the indirection operator next to the variable
name:
int *x;
// or even...
int * x;
I happen to think that the syntax I use makes the most sense, but
others could probably argue that their way is best, too. In the end,
settle on the method you like best and then stick to it.
NEW TERM
NOTE
NOTE
74 Day 3
Putting It Together
Let’s try to tie together what you have learned in the previous section. I’ll take the MAILLIST
program from Day 2, “Wading In Deeper,” and modify it so that it uses dynamic memory
allocation. This will require a few changes. First, take a look at the modified program, and
then I’ll explain the changes. Listing 3.1 contains the modified MAILLIST program.
Listing 3.1. POINTER.CPP.
1: #include
2: #include
3: #include
4: #pragma hdrstop
5: #include “structur.h”
6: void displayRecord(int, mailingListRecord mlRec);
7: int main(int, char**)
8: {
9: //
10: // create an array of pointers to
11: // the mailingListRecord structure
12: //
13: mailingListRecord* listArray[3];
14: //
15: // create an object for each element of the array
16: //
17: for (int i=0;i<3;i++)
18: listArray[i] = new mailingListRecord;
19: cout <<>
20: int index = 0;
21: //
22: // get three records
23: //
24: do {
25: cout << “First Name: “;
26: cin.getline(listArray[index]->firstName,
27: sizeof(listArray[index]->firstName) - 1);
28: cout << “Last Name: “;
29: cin.getline(listArray[index]->lastName,
30: sizeof(listArray[index]->lastName) - 1);
31: cout << “Address: “;
32: cin.getline(listArray[index]->address,
33: sizeof(listArray[index]->address) - 1);
34: cout << “City: “;
35: cin.getline(listArray[index]->city,
36: sizeof(listArray[index]->city) - 1);
37: cout << “State: “;
38: cin.getline(listArray[index]->state,
39: sizeof(listArray[index]->state) - 1);
40: char buff[10];
41: cout << “Zip: “;
Up to Your Neck in C++ 75
3
42: cin.getline(buff, sizeof(buff) - 1);
43: listArray[index]->zip = atoi(buff);
44: index++;
45: cout <<>
46: }
47: while (index <>
48: //
49: // display the three records
50: //
51: clrscr();
52: //
53: // must dereference the pointer to pass an object
54: // to the displayRecord function.
55: //
56: for (int i=0;i<3;i++)>
57: displayRecord(i, *listArray[i]);
58: }
59: //
60: // ask the user to choose a record
61: //
62: cout << “Choose a record: “;
63: char rec;
64: do {
65: rec = getch();
66: rec -= 49;
67: } while (rec <> 2);
68: //
69: // assign the selected record to a temporary variable
70: // must dereference here, too
71: //
72: mailingListRecord temp = *listArray[rec];
73: clrscr();
74: cout <<>
75: //
76: // display the selected recrord
77: //
78: displayRecord(rec, temp);
79: getch();
80: return 0;
81: }
82: void displayRecord(int num, mailingListRecord mlRec)
83: {
84: cout << “Record “ <<>
85: cout << “Name: “ <<>
86: cout <<>
87: cout <<>
88: cout << “Address: “ <<>
89: cout <<>
90: cout <<>
91: cout <<>
92: cout <<>
93: cout <<>
94: }
76 Day 3
First, on line 13 I declared the listArray array as an array of pointers. Following that,
I created objects for each element of the array. This takes place in the for loop on
lines 17 and 18. After that, I changed the direct membership operators (.) to indirect
membership operators (->). I also have to dereference the pointers on line 57 and again on
line 72. This is necessary because an object is expected and we cannot use a pointer in place
of an object. Notice that the displayRecord function (starting on line 82) doesn’t change. I
haven’t changed the fact that the mailingListRecord structure is passed to the function by
value, so the code in the function doesn’t need to be modified.
If you’ve had previous experience with C++, you may have noticed that this program has a
bug in it. I’ll let you in on the secret before the end of the chapter.
References
A reference is a special type of pointer that allows you to treat a pointer like a regular
object.
References, like pointers, can be confusing. A reference is declared using the reference operator.
The symbol for the reference operator is the ampersand (&) which is the same symbol used
for the address-of operator (don’t worry, the compiler knows how to keep it all straight). As
I said, a reference allows you to treat a pointer like an object. Here’s an example:
MyStruct* pStruct = new MyStruct;
MyStruct& ref = *pStruct;
ref.X = 100;
Notice that with references you use the direct member operator rather than the indirect
member operator as you do with pointers. Now you can get rid of all of those pesky ->
operators! Although you won’t use references a lot, they can be very handy when you need
them. By the way, this code snippet could be condensed a little. Here’s how I would write
it in a real program:
MyStruct& ref = *new MyStruct;
ref.X = 100;
Although this might look odd, it does exactly the same thing as the first example. Combining
statements like this is common and avoids unnecessary overhead.
Let’s go once more to the MAILLIST example. This time I’ll modify it by implementing a
reference in the do-while loop. Actually, I’ll be modifying the POINTER example found in
Listing 3.1. The new program, found in Listing 3.2, illustrates this change.
ANALYSIS
NEW TERM
Up to Your Neck in C++ 77
3
Listing 3.2. REFERENC.CPP.
1: #include
2: #include
3: #include
4: #pragma hdrstop
5: #include “structur.h”
6: void displayRecord(int, mailingListRecord mlRec);
7: int main(int, char**)
8: {
9: cout <<>
10: //
11: // create an array of mailingListRecord structures
12: //
13: mailingListRecord* listArray[3];
14: //
15: // create objects for each record
16: //
17: for (int i=0;i<3;i++)
18: listArray[i] = new mailingListRecord;
19: int index = 0;
20: //
21: // get three records
22: //
23: do {
24: // create a reference to the current record
25: mailingListRecord& rec = *listArray[index];
26: cout << “First Name: “;
27: cin.getline(rec.firstName, sizeof(rec.firstName) - 1);
28: cout << “Last Name: “;
29: cin.getline(rec.lastName, sizeof(rec.lastName) - 1);
30: cout << “Address: “;
31: cin.getline(rec.address, sizeof(rec.address) - 1);
32: cout << “City: “;
33: cin.getline(rec.city, sizeof(rec.city) - 1);
34: cout << “State: “;
35: cin.getline(rec.state, sizeof(rec.state) - 1);
36: char buff[10];
37: cout << “Zip: “;
38: cin.getline(buff, sizeof(buff) - 1);
39: rec.zip = atoi(buff);
40: index++;
41: cout <<>
42: }
43: while (index <>
44: //
45: // display the three records
46: //
47: clrscr();
48: //
49: // must dereference the pointer to pass an object
50: // to the displayRecord function.
51: //
continues
78 Day 3
52: for (int i=0;i<3;i++)>
53: displayRecord(i, *listArray[i]);
54: }
55: //
56: // ask the user to choose a record
57: //
58: cout << “Choose a record: “;
59: char rec;
60: do {
61: rec = getch();
62: rec -= 49;
63: } while (rec <> 2);
64: //
65: // assign the selected record to a temporary variable
66: // must dereference here, too
67: //
68: mailingListRecord temp = *listArray[rec];
69: clrscr();
70: cout <<>
71: //
72: // display the selected recrord
73: //
74: displayRecord(rec, temp);
75: getch();
76: return 0;
77: }
78: void displayRecord(int num, mailingListRecord mlRec)
79: {
80: cout << “Record “ <<>
81: cout << “Name: “ <<>
82: cout <<>
83: cout <<>
84: cout << “Address: “ <<>
85: cout <<>
86: cout <<>
87: cout <<>
88: cout <<>
89: cout <<>
90: }
The only real change is in the do-while loop. Notice that a reference to a
mailingListRecord structure is declared. Each time through the loop, the reference
is assigned a different object (the next element in the array). Notice that I got rid of the
indirect membership operators and replaced them with the direct membership operators. As
I said earlier, a reference allows you to treat a pointer as an object. What that does for us in
this case is clean up the code a little and make it easier to read. Oh, for those of you keeping
score, this program has the same bug in it as does the POINTER example. I’ll remedy that at the
end of the chapter.
Listing 3.2. continued
ANALYSIS
Up to Your Neck in C++ 79
3
Although it might appear that references are preferred over pointers, that is not the case.
References have some peculiarities that make them unsuitable in many cases. For one thing,
references cannot be declared and then later assigned a value. They must be initialized when
declared. For instance, the following code snippet will result in a compiler error:
MyStruct* pStruct = new MyStruct;
MyStruct& ref;
ref = *pStruct;
ref.X = 100;
Another problem with references is that they cannot be set to 0 or NULL as pointers can. That
means you’ll have to take special care to ensure that a reference is not deleted twice. References
and pointers can often serve the same purpose, but neither is perfect in every programming
situation.
Passing Function Parameters by
Reference and by Pointer
Earlier I talked about passing objects to functions by value. I said that in the case of structures
and classes, it is usually better to pass those objects by reference rather than by value. Any
object can be passed by reference. This includes the primitive data types such as int and long,
as well as instances of a structure or class. To review, when you pass function parameters by
value, a copy of the object is made, and the function works with the copy. When you pass
by reference, a pointer to the object is passed and not the object itself. This has two primary
implications. First, it means that objects passed by reference can by modified by the function.
Second, passing by reference eliminates the overhead of creating a copy of the object.
The fact that an object can be modified by the function is the most important aspect of
passing by reference. Take this code, for instance:
void IncrementPosition(int& xPos, int& yPos)
{
xPos++;
yPos++;
}
int x = 20;
int y = 40;
IncrementPosition(x, y);
// x now equals 21 and y equals 41
Notice that when the function returns, both of the parameters passed have been incremented
by one. This is because the function is modifying the actual object via the pointer (remember
that a reference is a type of pointer).
80 Day 3
Remember that a function can return only one value. By passing
parameters by reference you can achieve the effect of a function
returning more than one value. The function still only returns one
value, but the objects passed by reference are updated, so the function
effectively returns multiple values.
As I said, the other reason to pass parameters by reference is to eliminate the overhead of
making a copy of the object each time the function is called. When dealing with primitive
data types, there is no real overhead involved in making a copy. When dealing with structures
and classes, however, the overhead is something to be considered. You should pass structures
of any consequence by reference, as the following code demonstrates:
// structure passed by reference
void someFunction(MyStructure& s)
{
// do some stuff with ‘s’
return;
}
MyStructure myStruct;
// do some stuff, then later...
someFunction(myStruct);
Notice that the function call looks exactly the same whether the object is being passed by
reference or by value.
Do you see a potential problem with passing by reference? If you pass by reference, you avoid
the overhead of making a copy of the object, but now the object can be modified by the
function. Sometimes you don’t want the object to be modified by the function. So what if you
want to pass by reference but make sure the object is not modified? Read on and I’ll tell you.
The const Keyword
The const keyword will allow you to declare a variable as constant.
Once a variable is declared with const, it cannot be changed. The solution, then, is to pass
by reference and make the object const:
void someFunction(const MyStruct& s)
{
// do some stuff with ‘s’
return;
}
MyStructure myStruct;
// later
someFunction(myStruct);
TIP
NEW TERM
Up to Your Neck in C++ 81
3
Now you are free to pass by reference and not worry that your object might be modified by
the function. Note that the function call itself stays the same and that only the function
definition (and declaration) is modified with the const keyword.
If you attempt to modify a const object, you will get a compiler error
stating, Cannot modify a const object. The following code will
generate that error message:
void someFunction(const MyStruct& s)
{
s.dataMember = 100; // cannot modify a const object
return;
}
Once you declare an object as const, the compiler will make sure you
don’t modify the object.
Note that the object is const only within the function. The object can be modified both
before and after the function returns (provided it was not initially declared as const).
Passing by pointer is essentially the same as passing by reference. Passing by pointer has a
couple of syntactical headaches that make it less desirable than passing by reference. Let’s take
the IncrementPosition() function from the first example in this section and modify it to pass
by pointer rather than by reference:
void IncrementPosition(int* xPos, int* yPos)
{
*xPos++; // dereference, then increment
*yPos++;
}
Note that the pointer has to be dereferenced before it can be incremented. Most of the time
your needs will be best served by passing by reference, but you may pass by pointer if a
situation dictates the need. When passing char arrays, you will usually pass by pointer rather
than by reference because you can use a pointer to a char array and the name of the array
interchangeably. When passing character arrays, it is better to pass by pointer.
The new and delete Operators
Up to this point I have been talking primarily about aspects of the C++ language that come
from C. From this point on we’ll be looking at features that are specific to the C++ language.
The new and delete operators are two important C++ language features.
NOTE
82 Day 3
As mentioned in the preceding section, memory in a C++ program is allocated dynamically
using operator new. You free memory using the delete operator. Unless you have previously
programmed in C, you might not appreciate the simplicity of new and delete. In C programs,
you use malloc(), calloc(), realloc(), and free() to dynamically allocate memory.
Windows really complicates things by offering a whole raft of local and global memoryallocation
functions. Although this is not exactly difficult, it can be confusing to say the least.
C++ removes those headaches through the use of new and delete.
A new World Order
You’ve already seen new in action, so let’s review. As discussed earlier, you can allocate
memory locally (from the stack) or dynamically (from the heap). The following code snippet
shows examples of allocating two character arrays. One is allocated from the stack (local
allocation), and the other is allocated from the heap (dynamic allocation):
char buff[80];
char* bigBuff = new char[4096];
In the first case the buffer size is insignificant, so it doesn’t really matter whether the stack
or the heap is used. In the second case a large char array is needed, so it makes sense to allocate
it from the heap rather than from the stack. This preserves stack space. In the case of arrays
(remember, a string is just an array of type char), the dynamic and local flavors can be used
interchangeably. That is, they use the same syntax:
strcpy(buff, “Ricky Rat”);
strcpy(bigBuff, “A very long string that goes on and on...”);
// later on...
strcpy(bigBuff, buff);
Remember that the name of an array when used by itself points to the first memory location
of the array. A pointer also points to the first memory location of the array, so that is why the
two forms can be used interchangeably.
If the new operator fails to allocate the requested memory, it returns
NULL. In theory, you should check the pointer after calling new to ensure
that it contains a non-zero value:
char* buff = new char[1024];
if (buff) strcpy(buff, “Buteo Regalis”);
else ReportError(); // something went wrong
In reality, if the new operator fails in a 32-bit Windows program, the
entire system is in trouble, and neither your program nor any other will
be running for long.
NOTE
Up to Your Neck in C++ 83
3
If you are attempting to allocate very large chunks of memory (several
megabytes in size) or are trying to allocate memory at critical points in
your program, you should check the pointer for validity before continuing.
For routine memory-allocation chores, you can probably get
by without checking to ensure that the new operator succeeded.
delete
All memory allocated must be deallocated (released or freed) after you are done with the
memory. With local objects, this happens for you automatically, and you don’t have to worry
about it. The memory manager allocates the memory your object needs from the stack and
then frees that memory when the object goes out of scope (usually when a function returns
or when the code block in which the object was declared ends). When using dynamic memory
allocation, the programmer must take the responsibility of freeing any memory allocated with
the new operator.
Freeing memory allocated with new is accomplished with the delete operator.
All calls to new need to have a matching delete. If you do not free all
memory allocated with the new operator, your program will leak
memory. You need to be diligent in matching new/delete pairs.
Using the delete operator is frightfully easy:
SomeObject* myObject = new SomeObject;
// do a bunch of stuff with myObject
delete myObject; // so long!
That’s all there is to it! There isn’t a lot to the delete operator, but there are a couple of things
about pointers and delete that you should be aware of. The first is that you must not delete
a pointer that has already been deleted, or you will get access violations and all sorts of other
fun stuff. Second, it is okay to delete a pointer that has been set to 0. So what does that mean
in the real world? Let me explain.
Sometimes you declare a pointer just in case it might be used, but you don’t know for sure
whether it will be used in a given instance of your program. For example, let’s say you have
an object that is created if the user chooses a certain menu item. If the user never chooses that
NEW TERM
WARNING
84 Day 3
menu item, the object never gets created. So far, so good. The problem is that you need to
delete the pointer if the object was created, but not delete the pointer if the object was not
created. Deleting an initialized pointer is asking for trouble because you have no idea what
memory the pointer points to. There are two ways to work around this.
I said earlier that it is a good idea to initialize pointers to 0 if you don’t use them right away.
This is a good idea for two reasons. The first reason I explained earlier (uninitialized pointers
contain random values, which is undesirable). The second reason is because it’s okay to delete
a NULL pointer—you can call delete for that pointer and not worry about whether it was ever
used:
Monster* swampThing = 0;
// later when it’s time to exit the program...
delete swampThing; // so long, sucker!
In this case you don’t really care whether memory for the object was ever allocated because
the call to delete is safe whether the pointer points to an object or is NULL.
You may run into situations where delete could be called more than
once for an object. For instance, you may create an object in one part
of your program and delete it in another part of the program. A
situation might exist where the section of code that deletes the object
might never be executed. In that case you will also want to delete the
object when the program closes (for insurance). To avoid the possibility
of a pointer getting deleted twice, get into the habit of setting the
pointer to NULL or 0 after deleting it:
Monster* borg = new Monster;
// later....
delete borg;
borg = 0;
Now, if delete is called twice for the object, it won’t matter because it’s
okay to delete a NULL pointer.
Another way around the double-delete problem is to check the pointer for a non-zero value
before calling delete:
if (swampThing) delete swampThing;
This assumes that you have been diligent in setting deleted pointers to 0 in other parts of the
program. It doesn’t matter which method you use, but be sure to use one of them in any case
where a pointer could accidentally be deleted twice.
TIP
Up to Your Neck in C++ 85
3
If you use a reference when dynamically creating an object, the syntax
for delete requires a twist. Here’s an example that illustrates this point:
MyStruct& ref = *new MyStruct;
ref.X = 100;
// later...
delete &ref;
Note that you need the address-of operator to delete the pointer in the
case of a reference. Remember that a reference cannot be set to 0, so
you must be careful not to delete a reference twice.
Another Mystery Solved
Have you figured it out yet? “Huh?” you say? The bug in the POINTER and REFERENC
programs… have you figured out what it is? You got it! The program leaks memory. I created
an array of structures allocated from the heap but never freed the memory. So what I need
is a couple of lines to clean things up just before the program ends:
getch(); // existing line
for (int i=0;i<3;i++)
delete listArray[i];
There! Now I have a properly behaving program. I just ran through the array of pointers and
deleted each one. Nuthin’ to it.
new[] and delete[]
When you call new to create an array, you are actually using the new[] version of the new
operator. It’s not important that you know the inner details of how that works, but you do
need to know how to properly delete arrays that are dynamically allocated. Earlier I gave you
an example of dynamically creating a character array. Here is the same code snippet except
with the delete[] statement added:
char buff[80];
char* bigBuff = new char[4096];
strcpy(buff, “Ricky Rat”);
strcpy(bigBuff, “Some very long string.”);
// later on...
delete[] bigBuff;
Notice that the statement calls delete[] and not just plain delete. I won’t go into a technical
description of what happens here, but this ensures that all elements in the array get properly
deleted. Be sure that if you dynamically allocate an array you call the delete[] operator to
free the memory.
NOTE
86 Day 3
HOUSE RULES: POINTERS AND DYNAMIC MEMORY ALLOCATION
n Be sure to initialize pointers to 0 if they are not used right away.
n Be sure not to delete a pointer twice.
n It is OK to delete pointers set to NULL or 0.
n Set pointers to NULL or 0 after deleting them.
n Dereference pointers to obtain the object the pointer points to.
Functions in C++
A function in C++ can do everything that a function can do in C. In addition, C++ functions
can do things that functions in C cannot. Specifically, this section looks at the following:
n Function overloading
n Default parameters
n Class member functions
n Inline functions
Function Overloading
C++ allows you to have functions that have the same name but take different parameters.
Function overloading is when you have two or more functions with the same name
but with different parameter lists.
Functions that share a common name are called overloaded functions.
On Day 1 I showed you an example program which contained a function called multiply().
Not surprisingly, this function multiplied two values together. The function took two
integers, multiplied them, and returned the result. But what if you wanted to have the
function multiply two floating-point numbers? In C you would have to have two functions:
// declarations for a program written in c
int multiplyInt(int num1, int num2);
float multiplyFloat(float num1, float num2);
short multiplyShort(short num1, short num2);
Wouldn’t it be a lot easier if you could just have a function called multiply() that would be
smart enough to know whether you wanted to multiply shorts, ints, or longs? In C++ you
NEW TERM
NEW TERM
Up to Your Neck in C++ 87
3
can create such a scenario thanks to function overloading. Here’s how the declarations for an
overloaded function look:
// declarations in C++
int multiply(int num1, int num2);
float multiply(float num1, float num2);
short multiply(short num1, short num2);
You still have to write separate functions for each of these declarations, but at least you can
use the same function name. The compiler takes care of calling the correct function based on
the parameters you pass the function. For example:
float x = 1.5;
float y = 10.5;
float result = multiply(x, y);
The compiler sees that two floats are passed to the function and calls the version of the
multiply() function that takes two floating-point values for parameters. Likewise, if two ints
are passed, the compiler calls the version of multiply() that takes two integers.
It is the parameter list that makes overloaded functions work. You can
vary either the type or the number of parameters a function takes (or
both), but you cannot create an overloaded function by changing just
the return value. For example, the following does not constitute an
overloaded function:
int DoSomething();
void DoSomething();
If you try to compile a program containing these lines, you will get a
compiler error that says, Type mismatch in redeclaration of
‘DoSomething()’. The two functions need to vary by more than just the
return value in order to have overloaded functions.
Compilers keep track of overloaded functions internally through a
process called name mangling. Name mangling means that the compiler
creates a function name that takes into account the parameter list of the
function. Internally, the compiler refers to the mangled name rather
than the plain text name you would recognize. For example, for the
multiply function taking two float values, the mangled name might be
multiply$qff.
NOTE
NOTE
88 Day 3
Let’s take a quick detour and talk about something you will need to use on occasion when
dealing with overloaded functions.
Meet the Cast
Using overloaded functions works fine as long as you use the proper data types when calling
an overloaded function. But what if you mix and match? In this case, you will need to cast
a variable or literal value.
A cast tells the compiler to temporarily treat one data type as if it were another.
A cast looks like this:
float x = (float)10 * 5.5;
In this case the cast tells the compiler, “Make the number 10 a float.” (The second number
is automatically interpreted as a float because it contains a decimal place.) Take a look at the
following code snippet:
int anInt = 5;
float aFloat = 10.5;
float result = multiply(anInt, aFloat);
In this case you will get a compiler error because there is an ambiguity between the parameters
passed and the function declarations. The compiler error, in effect, says, “I can’t figure out
from the parameters passed which version of multiply() to call.” The same error will be
produced if you use code like this:
int result = multiply(10, 10);
// is 10 a float, int or short?
Here the compiler cannot figure out whether the numeric constants are to be interpreted as
floats, ints, or shorts. When this occurs, you basically have two choices. First, you can
simply avoid using literal values in the function call. If you want to multiply two ints, you
can declare two int variables and pass those to the function:
int x = 10;
int y = 10;
int result = multiply(x, y);
Now there is no ambiguity because x and y are both obviously ints. That’s probably overkill
for simple situations, though. The other thing you can do is to cast the numeric constants
to tell the compiler what type to expect:
int result = multiply((int)10, (int)10);
NEW TERM
Up to Your Neck in C++ 89
3
Now the compiler knows to treat the literal values as ints. A cast is also used to temporarily
force the compiler to treat one data type as if it were something else. Let’s go back to the first
example in this section and this time cast one of the variables to remove the ambiguity:
int x = 5;
float y = 10.5;
float result = multiply((float)x, y);
In this case x is an int, but you are casting it to a float, thereby telling the compiler to treat
it as a float. The compiler happily calls the float version of multiply() and goes on its way.
Ultimately, you want to write overloaded functions so that ambiguities do not exist and
casting is not necessary. In some cases that is not possible, and in those cases casting will be
required.
Default Parameters for Functions
A function in C++ can have default parameters which, as the name implies, supply
a default value for a function if no value is specified when the function is called.
A function implementing a default parameter might look like this:
// declaration, parameter ‘eraseFirst’ will be false by default
void Redraw(bool eraseFirst = false);
// definition
void Redraw(bool eraseFirst)
{
if (eraseFirst) {
// erase code
}
// drawing code
}
When this function is called, it can be called with or without a parameter. If the parameter
is supplied at the time the function is called, the function behaves as a regular function would.
If the parameter is not supplied when the function is called, the default parameter is used
automatically. Given this example, the following two lines of code are identical:
Redraw();
Redraw(false);
Note that when a parameter has a default value, it can be omitted from the function call
altogether. You can mix default and non-default parameters in the same function:
int PlaySound(char* name, bool loop = false, int loops = 10);
// call function
int res;
res = PlaySound(“chime.wav”); // does not loop sound
res = PlaySound(“ding.wav”, true); // plays sound 10 times
res = PlaySound(“bell.wave”, true, 5); // plays sound 5 times
NEW TERM
90 Day 3
Default parameters are helpful for many reasons. For one thing, they make your life easier.
You may have a function that you call with the same parameters 99 percent of the time. By
giving it default parameters, you shorten the amount of typing required each time you make
a call to the function. Whenever you want to supply parameters other than the defaults, all
you have to do is plug in values for the default parameters.
Any default parameters must come at the end of the function’s parameter
list. The following is not a valid function declaration:
int MyFunction(int x, int y = 10, int t = 5, int z);
In order for this function declaration to compile, the default parameters
must be moved to the end of the function list:
int MyFunction(int x, int z, int y = 10, int t = 5);
If you don’t put the default parameters at the end of the parameter list,
the compile will generate a compiler error.
Class Member Functions
As you will find out in this section, classes can contain their own functions. Such
functions are called member functions because they are members of a class.
Class member functions follow the same rules as regular functions: They can be overloaded,
they can have default parameters, they can take any number of parameters, and so on.
Class member functions can be called only through an object of the class to which the
function belongs. To call a class member function, you use the direct member operator (in
the case of local objects) or the indirect member operator (for dynamically created objects)
just like you did when accessing data members of a structure on Day 2. For example, let’s say
that you had a class called Airplane that was used to track an airplane for aircraft-control
software. That class would probably have the capability to retrieve the current speed of a given
aircraft via a function called GetSpeed(). The following example illustrates how you would
call the GetSpeed() function of an Airplane object:
Airplane plane; // create a class instance
int speed = plane.GetSpeed();
cout << “The airplane’s current speed is “ <<>
This code uses the direct membership operator to call the GetSpeed() function. Class member
functions are defined like regular functions except that the class name and scope-resolution
operator precede the function name. For example, the definition of the GetSpeed() function
might look like this in the source file:
NOTE
NEW TERM
Up to Your Neck in C++ 91
3
int Airplane::GetSpeed()
{
return speed; // speed is a class member variable
}
In this case, the scope-resolution operator tells the compiler that the GetSpeed() function is
a member of the Airplane class. I’ll talk more about class member functions when I discuss
classes tomorrow.
Tradition has it that class member function names begin with uppercase
letters. There is no hard and fast rule about this, but you will find
that most C++ programs follow this tradition. As a further note, I am
not a fan of the underscore character in function names. For example, I
much prefer the function name GetVideoRect() over the name
get_video_rect(). Regardless of what naming convention you use for
your functions, be consistent and use the same naming convention
throughout your programs.
Inline Functions
Normally a function only appears in the executable file once. Each section of code that uses
the function calls the function. This means that program execution jumps from the point
of the function call to the point in the program where the function resides. The statements
in the function are executed, and then the function returns. When the function returns,
program execution jumps back to the statement following the function call.
An inline function, as its name implies, is placed inline in the compiled code wherever
a call to that function occurs.
Inline functions are declared like regular functions but are defined with the inline keyword.
Each time the compiler encounters a call to an inline function in the source code, it places
a separate copy of the function’s code in the executable program at that point. Inline
functions execute quickly because no actual function call takes place (the code is already
inlined in the program).
Inline functions should be reserved for functions that are very small or
for those that need to be executed very quickly. Large functions or
those that are called from many places in your program should not be
inlined because your executable file will be larger as a result.
NOTE
NOTE
NEW TERM
92 Day 3
Inline functions are usually class member functions. Often the inline function definition (the
function itself) is placed in the header file following the class declaration. (This is the one time
that you can place code in your header files.) Because the GetSpeed() function mentioned
previously is so small, it can be inlined easily. Here’s how it would look:
inline int Airplane::GetSpeed() {
return speed; // speed is a class member variable
}
An inline function can also be defined within a class declaration. Because I haven’t talked
about classes yet, though, I’ll hold that discussion for tomorrow.
Summary
Wow, that’s some pretty heavy stuff ! Because you are reading this, you must still be left
standing. That’s good news. Today we got out the big guns and took on pointers and
references. Once you get a handle on pointers, you are well on your way to understanding
C++. As part of the discussion on pointers you learned about local versus dynamic memory
allocation, which led to a discussion about the new and delete operators. Today ends with
an explanation of how C++ extends the use of functions over what the C language provides.
Workshop
The Workshop contains quiz questions to help you solidify your understanding of the
material covered and exercises to provide you with experience in using what you have learned.
You can find answers to the quiz questions in Appendix A, “Answers to Quiz Questions.”
Q&A
Q Pointers and references confuse me. Am I alone?
A Absolutely not! Pointers and references are complicated and take some time to fully
understand. You will probably have to work with C++ a while before you get a
handle on pointers and references.
Q Do I always have to delete an object that I created dynamically with the new
operator?
A Yes and no. All objects created with new must have a corresponding delete, or the
program will leak memory. Some objects, however, have parent objects that will
take the responsibility for deleting them. So the question is not whether an object
created with new should be deleted, but rather who should delete it. You will always
want to call delete for classes you write. Later, when you learn about VCL (on
Up to Your Neck in C++ 93
3
Day 5, “C++ Class Frameworks and the Visual Component Model”), you will see
that VCL parent objects take the responsibility for deleting their children.
Q Should I create my objects on the stack or on the heap?
A That depends on the object. Large objects should be created on the heap in order
to preserve stack space. Small objects and primitive data types should be created on
the stack for simplicity and speed of execution.
Q What’s the point of having overloaded functions?
A Overloaded functions provide you a means by which you can have several functions
that perform the same basic operation and have the same function name, but take
different parameters. For example, you might have an overloaded function called
DrawObject(). One version might take a Circle class as a parameter, another might
take a Square class as a parameter, and a third could take a class called Polygon as a
parameter. By having three functions with the same name, you avoid the need to
have three different function names.
Q Should I use a lot of inline functions?
A That depends on the function, of course. In general, though, the answer is no.
Inline functions should be reserved for functions that are very small or seldom
used, or where execution speed is critical.
Quiz
1. What is a pointer?
2. What does it mean to dereference a pointer?
3. What is the return value of operator new?
4. Should instances of classes and structures be passed to functions by reference or by
value?
5. What does the const keyword do?
6. Does the following qualify as an overloaded function? Why or why not?
void MyFunction(int x);
long MyFunction(int x);
7. Which is better to use, a reference or a pointer?
8. What is a class member function?
9. How does the compiler treat an inline function as opposed to a regular function?
10. What, if anything, is wrong with the following code snippet?
char* buff = new char[200];
// later...
delete buff;
94 Day 3
Exercises
1. Write a program that declares a structure, dynamically creates an instance of the
structure, and fills the structure with data. (Hint: Don’t forget to delete the
pointer.)
2. Modify the program from Exercise 1 to use a reference rather than a pointer.
3. Rewrite the REFERENC program in Listing 3.2 so that the mailingListRecord
structure is passed to the displayRecord() function by reference rather than by
value.
4. What is wrong with the following function declaration?
void SomeFunction(int param1, int param2 = 0, int param3);
5. Explain to a five-year-old the difference between pointers and references.
Totally Immersed: C++ Classes and Object-Oriented Programming 95
