C++ Manager Functions
Items Covered By These Notes
- Constants
- Reference Types
- Constructors
- Destructors
- Copy Constructors
- Operator Overloading
Constants
In C you declared a constant using the #define statement. For example,
to specify that the boiling point of water is 212 degrees, you might
have written:
#define WATER_BOILING_POINT 212
In C++ you declare a constant using the const keyword. For
example:
const int WATER_BOILING_POINT = 212;
Notice that a constant declaration requires five elements:
- the keyword const
- the type of the constant (e.g., int)
- the name of the constant (e.g., WATER_BOILING_POINT)
- the value of the constant (e.g., 212)
- the '=' sign and the ';'.
The constant keyword can be used to declare local variables, global
variables, or parameters to be constant. For example,
the function declaration:
float compute_avg(const int scores [], const int size);
declares that both the scores array and the size of the scores array
are constant within the function. That means that within the function you
promise not to modify either the contents of the scores array or the
size variable. If you attempt to do so, the compiler will give you an
error message. For example:
#include <stdio.h>
float compute_avg(const int scores[], int size) {
int i, sum;
sum = 0;
for (i = 0; i < size; i++) {
sum += scores[i];
scores[i] = sum; // can't do this--scores is a constant
}
return ((float)sum / size);
}
main() {
int s[5] = {72, 68, 79, 81, 65};
printf("%6.2f\n", compute_avg(s, 5));
}
|
If you try to compile this program, you get the following error message
from the compiler:
UNIX> g++ temp.cc
avg.cc: In function `float compute_avg(const int *, int)':
avg.cc:8: assignment of read-only location
Reference Types
We will try to avoid using reference types in this class because they
are confusing. However, they appear in examples in both the Weiss and
Schildt books and therefore you need to have at least a basic understanding
of what they are. For example, if you have a class named string,
you will often see a method with the following signature:
string::string(const string &objToBeCopied);
This method is called a copy constructor and we will talk about it
later in these notes. The thing to focus on for now is the declaration
of the parameter objToBeCopied:
- objToBeCopied is declared to be a reference of type string. It
is also declared to be constant, which means that its contents
cannot be altered by the method.
- A reference type is defined by following the type specifier with
an address-of (&) operator:
- You can think of a reference type as being the same as a pointer.
The only difference is that when you refer to the contents of
the object pointed to by the reference type, you use the dot (.)
operator rather than the arrow (->) operator. For example, if
the string class contains a method called length, then
the copy constructor declared above could access the length method
in objToBeCopied via the notation objToBeCopied.length().
- Why does C++ confuse the issue by introducing what is in essence
a second type of pointer? The reasons are subtle and are not
important in this course. They will be discussed in CS365.
Constructors
You have already seen how constructors are used to initialize an object
when it is first created. Here are some specifics to remember about
constructors:
- They are automatically invoked when an object is created.
- They have the same name as the class name.
- They may be overloaded (i.e., there may be multiple constructors with
different signatures).
- They must not specify a return type or explicitly return a value.
- Example constructor declaration:
class Stack {
protected:
int *data;
int top;
public:
Stack( int maxSize );
Stack( );
...
};
- Example constructor definition:
Stack::Stack( int maxSize ) {
top = 0;
data = new int[maxSize];
}
Stack::Stack( ) {
top = 0;
data = new int[20]; // default maximum size
}
|
- If you do not specify any constructor functions, the C++ compiler
will create an implicit one for you that takes no arguments and that
does nothing.
Destructor Functions
You have already seen how destructors are used to clean up an object's
memory when it is destroyed. Here are some specifics to remember about
destructors:
- They are automatically invoked when an object is destroyed.
- They have the same name as the class name, except that they are preceded
by a ~.
- They take no arguments. Hence they
may not be overloaded (i.e., there
may be only one destructor per class).
- They must not specify a return type or explicitly return a value.
- Example destructor declaration and definition:
~Stack() { delete [] data; }
|
- A destructor does not deallocate memory for its own object. However,
it should delete any memory that the constructor explicitly allocated for
member objects using the new operator.
- If you do not specify any destructor functions, the C++ compiler
will create an implicit one for you that takes no arguments and that
does nothing.
Creating and Destroying Objects
- An object can be statically allocated by simply declaring it:
Stack a(5);
Stack b;
Stack c(a);
- A statically allocated object cannot be explicitly destroyed. However,
if the block or procedure to which the object belongs goes out of
scope, then the object will be automatically destroyed.
- An object can be dynamically allocated using the new
operator:
Stack *x = new Stack(4);
Stack *y;
Stack *z = new Stack[5]; // allocate an array of 5 Stacks
y = new Stack(*x);
- A dynamically allocated object can be freed using the delete
operator:
delete x;
delete [] z;
- If an array is allocated using the new command, it must be deleted
by placing [] after the delete command. If you fail to do this,
the compiler will think that the object being deleted is a singleton,
and it will call the destructor on only the first object.
Copy Constructor
The copy constructor copies the contents of one object to another object.
The two objects must be members of the same class.
- The copy constructor is called when:
- The form of the copy constructor is classname(const classname&)
where classname is the name of the class. The argument to the
copy constructor is the object to be copied.
- The copy constructor should copy the values of each of the parameter
object's data members to each of the class's data members.
- IMPORTANT: If a data member is a pointer, the copy constructor
should not copy the pointer. Instead, it should:
- Allocate a new block of memory large enough to hold the contents
of the object being pointed to by the pointer,
- Copy the object to the newly allocated memory, and
- Make the data member point to the new block of memory
As an example, suppose you have a string class:
class string {
public:
string(const string&);
protected:
char *str;
int length;
}
Then the copy constructor should look something like:
string::string(const string & s) {
length = s.length;
str = new char [ length + 1 ];
if ( str == 0 ) {
fprintf(stderr, "string::string: could not allocate a string of length %d\n",
length);
exit(1);
}
strcpy( str, s.str );
}
|
The problem with copying the pointer rather than the contents of the
object to which the pointer points is that you end up with two objects
pointing to the same block of memory. If either of the objects goes
out of scope or is destroyed using the delete operator, then the other
object will be left with a dangling pointer.
- Important: If your class contains pointers to dynamically allocated
memory, define a copy constructor for that class that adheres to the
above convention of allocating memory for the object pointed to by a
pointer and copying the object to that memory. If you fail to define
a copy constructor, C++ will define a default copy constructor that
will do a bit-wise copy on all the data members. This means that pointers
will be copied and you can end up with dangling pointers as described
above.
Operator Methods
- C++ allows most of its operators (e.g., =, ==, +, -, ++, ->) to be
redefined by a class. This
redefinition is called overloading, because the operator
has different definitions depending on which object it is being
invoked on.
- Examples:
class string {
public:
string& operator=(const string &);
string& operator+=(const string&);
string& operator+=(const char*);
...
}
string& string::operator=(const string &s)
// don't do anything if the object to be copied is the same as
// the current object
if (*this != s) {
length = s.length;
delete [] str; // free the memory for the old string
str = new char [ length + 1 ]; // allocate memory for the new string
if ( str == 0 ) {
fprintf(stderr, "string::string: could not allocate a string of length %d\n",
length);
exit(1);
}
strcpy( str, s.str );
}
}
string& operator+=(const string& s) {
len += s.len;
char *p = new char[len+1];
assert( p != 0);
strcpy(p, str);
strcat(p, s.str);
delete [] str; // free up old memory!
str = p;
return *this;
}
string& operator+=(const char *s) {
len += strlen(s);
char *p = new char[len+1];
assert( p != 0);
strcpy(p, str);
strcat(p, s);
delete [] str; // free up old memory!
str = p;
return *this;
}
|
- Operators should take care to release memory if they are replacing
one piece of dynamically allocated memory with another (see the
assignment operator for string and the concatenate operator for
string as examples).