C++ Manager Functions


Items Covered By These Notes

  1. Constants
  2. Reference Types
  3. Constructors
  4. Destructors
  5. Copy Constructors
  6. 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 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:
  1. 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.

  2. A reference type is defined by following the type specifier with an address-of (&) operator:

  3. 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().

  4. 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:

  1. They are automatically invoked when an object is created.

  2. They have the same name as the class name.

  3. They may be overloaded (i.e., there may be multiple constructors with different signatures).

  4. They must not specify a return type or explicitly return a value.

  5. Example constructor declaration:
    	    class Stack {
    	      protected:
    	        int *data;
    		int top;
    	      public:
         	        Stack( int maxSize );
    	        Stack( );
    		...
    	     };
    

  6. Example constructor definition:
                Stack::Stack( int maxSize ) {
    	      top = 0;
    	      data = new int[maxSize];
    	    }
    
    	    Stack::Stack( ) {
    	      top = 0;
    	      data = new int[20];  // default maximum size
    	    }
    

  7. 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:
  1. They are automatically invoked when an object is destroyed.

  2. They have the same name as the class name, except that they are preceded by a ~.

  3. They take no arguments. Hence they may not be overloaded (i.e., there may be only one destructor per class).

  4. They must not specify a return type or explicitly return a value.

  5. Example destructor declaration and definition:
    	    ~Stack() { delete [] data; }
    

  6. 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.

  7. 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

  1. An object can be statically allocated by simply declaring it:
         Stack a(5);
         Stack b;
         Stack c(a);
    

  2. 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.

  3. 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);
    

  4. A dynamically allocated object can be freed using the delete operator:
         delete x;
         delete [] z;
    

  5. 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.

  1. The copy constructor is called when:

  2. 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.

  3. The copy constructor should copy the values of each of the parameter object's data members to each of the class's data members.

  4. IMPORTANT: If a data member is a pointer, the copy constructor should not copy the pointer. Instead, it should:

    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.

  5. 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

  1. 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.

  2. 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;
         }
    
         

  3. 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).