Appendix B. The Prevention of Memory Leakage

One of the most dangerous pitfalls affecting the C++ community is that of memory leakage, or the unintentional nondeletion of dynamically allocated memory. A system inflicted with this problem will run correctly for some period of time, often weeks, with no discernable problem. It will then “seize up,” that is, run out of heap space for no apparent reason. The operator typically reboots the application, which again runs problem-free. The danger of this situation is that it is not caught at compile, link, or test-run time. It is detected only after the product has been shipped to a number of clients.

While memory leakage has occurred in standard C applications, it is typically introduced into that environment when a developer dynamically allocates space and simply forgets to free it. The problem is accentuated in C++ because most memory leakage occurs through the compiler's implicit function calls, making detection dependent on the developer knowing the inner workings of C++. In the course of much C++ development, I have identified eight programming areas prone to C++ memory leakage. These range from the mundane and obvious, such as forgetting to delete space that was explicitly allocated, to the more esoteric reasons, such as confusing arrays of pointers with arrays of objects, forgetting to make a base class destructor virtual, missing copy constructors, missing overloaded assignment operators, missing size arguments to the delete function, and using a poor design when constructing overloaded operators. The following text details each of these problem areas and provides a C++ coded example for each. Developers armed with these examples will be able to relegate memory leakage to the class of pitfalls that are infrequently encountered and easily overcome.

Leak #1

Mismatched new/delete calls in the constructor and destructor of a class. The obvious case of C++ memory leakage occurs when a developer dynamically allocates memory via new and forgets to explicitly clean up that memory with a call to delete. This can occur when an object is created on the heap and not explicitly cleaned up or, more commonly, when the constructor of a class dynamically allocates memory but the destructor is either absent or not cleaning up the object correctly. An example of the former case is shown here.

Example Code for Leak #1

class Point {
  int x, y;
  char* color;
public:
  Point(int, int, char*);
};
// Note that the constructor allocates space
// for the ''color'' data member but there is
// no destructor to free this memory.

Point:: Point(int new_x, int new y, char* col)

{

  x = new_x;  y = new_y;
  color = new char[strlen(col)+1];
  strcpy(color, col);

}

// The proper destructor would look like:
Point::~Point()

{ delete color;

}

Leak #2

Nested object pointers that are not cleaned up properly. A less obvious pitfall exists when one object contains another object by reference instead of by value. In the example code shown here, a Meal object contains a Melon object by reference (i.e., a Meal contains a pointer to a Melon). This Melon is dynamically allocated in the Meal's constructor and therefore must be deallocated in its destructor. The confusion occurs when a developer thinks that the Melon's destructor is called automatically by the Meal's destructor. Automatic destructor calls will occur when one object contains another by value. In our example, this would translate to a Meal object that contains a Melon object (i.e., by value).

Example Code for Leak #2

#include <iostream.h> 
class Melon {
 char* variety;
public:
 Melon(char* var);
 ~Melon();
 void print();

};

Melon::Melon(char* var)
{
 variety = new char[strlen(var)+1];
 strcpy(variety, var);
}

Melon::~Melon()
{
 delete variety;
}

void
Melon::print()
{
 cout << ''I'm a '' << variety << ''Melon
'';
}
class Meal {
 char* restaurant;
 Melon* m;
public:
 Meal(char* var, char* res);
 ~Meal();
 void print ();
};

Meal::Meal(char* var, char* res)
{
 m = new Melon(var);
 restaurant = new char[strlen(res)+1];
 strcpy(restaurant, res);
}

// Oops! Forgot to delete the contained Melon.
// This would not be necessary if Melon were a
// contained object as opposed to a pointer to an
// object.
Meal::~Meal()
{
 delete restaurant;
}
// The correct destructor would be defined
//as follows:
/* Meal::~Meal()
{
 delete restaurant;
 delete m;
} */
void
Meal::print()
{
 cout << ''I'm a Meal owned by '';
 m->print();
}

main()
{
 Mean ml (''Honeydew'' , ''Four Seasons''),
 Meal m2 (''Cantaloup'', ''Brook Manor Pub''),
 ml.print(); m2.print();
}

Leak #3

Deleting an array of objects without using the square brackets on delete. A common point of confusion for C++ novices involves the use of the square brackets on the delete function; for example, delete[] p versus delete p. This size argument is unnecessary for deleting a single object, a single primitive data type (e.g., int, char, short), or an array of a primitive type. It is required for deleting arrays of any object that has a destructor defined on it. Variable-sized objects are a particularly vulnerable category of objects affected by this constraint. A variable-sized object is an object that contains a pointer to dynamically allocated space. (Note: For a more detailed discussion of variable-sized classes/objects and their ramifications, see “Towards a Minimal Public Interface for C++ Classes” and “A Framework for Variable-Sized Classes,” The C++ Insider, Vol. 1, nos. 1 and 2, respectively.) The square brackets tell the compiler that the pointer is pointing to a vector of objects and that the destructor must be called along with the proper values for the object addresses (i.e., &array[0], &array[1], &array[2], etc.). If the brackets are not provided, then the pointer is assumed to be pointing at one object. The additional objects in the array will not have their destructors called, resulting in memory leakage. If a number is placed between the square brackets and is larger than the size of the array, then the compiler will call the destructor on invalid objects (memory overflow), resulting in heap corruption. If a number between square brackets is smaller than the size of the array, the compiler will not call the necessary number of constructors, resulting in memory leakage.

In all cases of memory leakage due to missing square brackets, the space taken up by the class (sizeof (class_name)) is put back on the heap because of the nature of new and delete. For this reason, some C++ developers claim the size argument is unnecessary for classes that do not have destructors defined on them. This is a dangerous coding convention since many such classes become variable-sized or have destructors added as the application matures. For example, a Point class containing two integer data members (x and y) might get a dynamically allocated string called color, (see Figure B.1), or a Meal class might add a melon pointer or even a melon object.

Graphical representation of memory leak #3.

Figure B.1. Graphical representation of memory leak #3.

Some developers consider this memory leakage problem to be the worst because it cannot be eliminated by the class implementor since C++ is a multiparadigm language (i.e., it contains nonclass/object entities. In a pure system, an array class would handle the deletion correctly for all users. Of course, arrays could be made a C+ + class for a given application, eliminating this particular memory leakage problem.

Example Code for Leak #3

// The following code demonstrates the 
// memory leakage problems associated
// with deleting a dynamically allocated
// array of Point objects.

#include <iostream.h>
class Point {
 int x, y;
 char* color;
public:
 Point(int=0, int=0, char*=''Red''),
 ~Point();
};

{
 x = new_x;
 y = new_y;
 color = new char[strlen(col)+1];
 strcpy(color, col);
}

Point::~Point()
{
 delete color;
 cout << ''In the destructor
'';
}
Point::Point(int new_x, int new_y, char* col)
main()
{
 Point *p = new Point[5];
 // Note the missing square brackets. This
 // statement is identical to ''delete[1] p;''.
 // It will call the destructor once with
 // the address of &p[0] as an argument. The
 // color string of the first Point object is
 // put back on the heap followed by the
 // memory occupied by the five Point objects.
 // The memory leaked is the color strings for
 // Points two through five (indices 1 to 4).

 delete p;
 // The correct statement is :
 // delete[] p; or delete[5] p;
}

Leak #4

Arrays of pointers to objects are not arrays of objects. Another point of confusion related to the use of delete's square brackets is the fact that destroying arrays of object pointers is very different from destroying arrays of objects. As was shown in the preceding code, the square brackets on the delete function control the proper calls to each object's destructor when used for destroying arrays of objects. Some developers attempt to use the size argument as a method for controlling proper destructor calls for objects stored in arrays of pointers to objects. Adding a size argument to the delete call on an array of pointers to objects simply tells the compiler the number of pointers to destroy. Since pointers are considered primitive types in C++, this argument has no effect on destructor calls. The pointers are placed back on the heap, but all of the objects are kept in their allocated state. The result is memory leakage.

Example Code for Leak #4

#include <iostream. h> 
class Point {
 int x, y;
 char* color;
 Point(int=0, int=0, char*=''Red''),
 ~Point();
};
Point::Point(int new_x, int new_y, char* col)
{
 x = new_x; y = new_y;
 color = new char[strlen(col)+1];
 strcpy(color, col);
}
Point::~Point()
{
 delete color;
}
main()
{
// The following line of code dynamically
// allocates an array of 10 pointers to
// Point objects (not the objects themselves).

 Point **p = new Point*[10];
 int i;
// The loop below allocates one Point object
// for each of the Point pointers.
 for (i=0; i<10; i++) {
  p[i] = new Point(i, i, ''Green''),
 }
// The following statement does not clean up
// the individual points, just their pointers. It
// results in the leakage of memory
// (10*sizeof(Point) + 60 bytes of space).
// Note: The 60 bytes are incurred for the
// storage of the string ''Green'' in each of the
// 10 objects.
   delete[]p; // or delete[10] p;
   // The correct code is as follows:
   /*
      for (i=0; i < 10; i++) {
       delete p[i];
      }
      delete p;
   */
}

Leak #5

Missing copy constructors. If the user of a C++ class attempts to invoke a constructor function that the implementor of that class has not defined, then the user expects to get an error from the compiler or, at a minimum, the linker. This is normally the case in C++, unless that constructor happens to be the copy constructor (also called the initialization constructor). The copy constructor is a constructor function whose sole argument is a reference to an object of the constructor's class. For example, the prototype of the copy constructor for the Point class looks like Point (const Point&);. If this constructor is not defined, then the compiler assumes its definition to be a memberwise copy of the data members (bitwise copy in C++ version 1.2). Unfortunately, memberwise (or bitwise) copying of a pointer is defined as copying the address from one location to another. The ramifications of this implicit pointer copying is that two objects will have pointers to the same dynamically allocated memory. When the first object is destroyed, its destructor will clean up the dynamically allocated memory associated with it. When the second object is destroyed, its destructor attempts to clean up the same memory. Deleting the same memory twice is considered an error and will probably corrupt the heap. A developer may argue that, by convention, no user of his or her class will call the copy constructor. Aside from the usual folly of programming by convention, there are issues dictating that a copy constructor be explicitly defined. Most calls to the copy constructor are not explicit calls. The copy constructor is called whenever one object is initialized to another of the same type, an object is passed by value as an argument to a function, or an object is returned by value from a function. This implicit behavior will cause memory leakage in variable-sized classes.

Example Code for Leak #5

#include <iostream.h> 
class Point {
 int x, y;
 char* color;
public:
 Point(int, int,char*);
// Note the commented-out copy constructor.
//Point(const Point&);
 ~ Point();
 Point duplicate(Point);
 void print();
};
Point::Point(int new_x, int new_y, char* col)
{
  x = new_x; y = new_y;
  color = new char[strlen(col)+1];
  strcpy(color, col);
}
// This is an example copy constructor for the
// Point class. Note that it is commented out
// of the application.
/*
Point::Point(const Point& rhs)
{
 x = rhs.x; y = rhs.y;
 color = new char[strlen(rhs.color)+1];
 strcpy(copy, rhs.color);
}
*/
Point::~Point()
}
 delete color;
}
// This function takes a Point object as an
// argument ''by value. '' That will cause an
// implicit call to the copy constructor.
// In addition, the C++ statement
// ''return(*this); '' will cause an implicit call to
// the copy constructor because
// the function returns a Point
// object by value.

Point
Point::duplicate(Point rhs)
{
 x = rhs.x;
 y = rhs.y;
 return(*this);
}

void
Point::print()
{
 cout << ''I'm a point at ('';
 <<x<<'' , '' <<y<< '') 
'';
 cout << ''My color is''; <<color<< ''. 

'';
}

main()
{
 Point p1(10, 10, ''Blue''),
 Point p2 (15, 18, ''Green''),
// The following declaration causes an
// implicit call to the copy constructor;
 Point p3 = p2;
// Another way of writing the above 
// declaration is:
// Point p3(p2);
 p1.print();
 p2.print();
 p3.print();
// The function ''duplicate'' takes a Point
// object by value and returns a local copy by
// value. The statement causes two implicit
// calls to the copy constructor.
 p1.duplicate(p2);
 p1.print();
 p2.print();
 p3.print();
}

Leak #6

Missing overloaded assignment operator. The memory leakage problem associated with a missing overloaded assignment operator is similar to the problem associated with the missing copy constructor. When an operator is used on an object of a class, the compiler (or linker) will generate an error if that operator is not overloaded for that class. The C++ language makes an exception with the assignment operator. If the user does not specify its functionality, then C++ guesses that the user wants to perform a memberwise copy (bitwise copy in versions 1.2 and earlier). If the class is variable-sized, then the result is memory leakage. The actual leakage occurs when the object on the left-hand side of the assignment operator has its internal address overlayed with the internal address of the object on the right-hand side of the operator. The memory to which the left-hand side points is no longer referenced but is still allocated. In addition, the two operand objects point at the same dynamically allocated memory (see Figure B.2). This will probably cause heap corruption when the destructor for the class is invoked by each object (at some later execution time) and each invocation attempts to delete the same address.

Memory leakage due to a missing assignment operator.

Figure B.2. Memory leakage due to a missing assignment operator.

Example Code for Leak #6

#include <iostream.h> 
class Point {
 int x, y;
 char* color;
public:
 Point(int, int, char*);
 Point(const Point&);
 ~Point();
 void print();
// Note the commented-out operator=
// const Point& operator=(const Point& rhs);
};
Point::Point(int new_x, int new_y, char* col)
{
 x = new_x; y = new_y;
 color = new char[strlen(col)+1];
 strcpy(color, col);
}
Point::Point(const Point& rhs)
{
 x = rhs.x; y = rhs.y;
 color = new char[strlen(rhs.color)+1];
 strcpy(color, rhs.color);
Point::-Point()}
{
 delete color;
}

void
Point::print()
{
 cout << ''I'm a point at ('';
 cout << x <<'', '' << y << '')
'';
 cout << ''My color is '' <<color<<''.

'';
}
// The following operation is a sample
// implementation of the overloaded assignment
// operator for the Point class. It is important that
// the function allow for assignment of one object
// to itself. The returned reference is marked
// constant so that the return value of overloaded
// operator= cannot be used as an lvalue.
// That is, (x=y) = z is illegal.
/*
const Point&
Point::opertor=(const Point& rhs)
{
// Avoid assignment to self.
 if(this == &rhs) {
  return(*this);
 }
 x = rhs.x; y = rhs.y;
 delete color;
 color = new char[strlen(rhs.color)+1];
 strcpy(color, rhs.color);
 return(*this);
}
*/

main()
{

 Point p1(10, 10, ''Blue''),
 Point p2(15, 18, ''Green");
 p1.print(); p2.print();
 p2=p1;
 p1.print(); p2.print();
}

Leak #7

General confusion concerning the overloading of nonmodifying operators. A nonmodifying operator is an operator that does not affect any of its operands and evaluates to an object of the same type as the operands. Typical examples include the mathematical operators like addition (+), subtraction (-), multiplication (*), division (/), and modulus (%). Relational operators are not considered nonmodifying, because they always evaluate to type boolean regardless of the data types being compared. In addition, assignment operators (e.g., =, +=, <<=) are not nonmodifying, because these operators affect their left-hand-side operand.

Memory leakage associated with incorrectly implemented, nonmodifying operators is the type of problem that either is nonexistent in an application or appears in almost every class. This discrepancy is due to the fact that it revolves around invalid assumptions about overloaded operators. The main issue involved with the implementation of nonmodifying operators revolves around the return value. For the sake of efficiency, a C++ developer may decide to return the evaluated result by reference instead of by value. (Recall that returning an object by value will implicitly call the copy constructor, costing runtime.) A first attempt at designing such an operation might be to build the evaluated result in a temporary variable and return it by reference. The Point example in the code here illustrates the fact that returning a local object by reference is an error since the memory associated with the reference is returned to the stack upon exit from the function. The caller is left with a returned reference to an invalid, deallocated object.

Returning a Reference to a Stack Object

#include <iostream.h> 
class Point {
 int x, y;
 char* color;
public:
 Point(int = 0, int = 0, char* = ''Red''),
 ~Point();
 Point (const Point&);
 void print();
 const Point& operator=(const Point&);
 const Point& operator+(const Point&);
};
void
Point::print()
{
 cout << ''I live at ('';
 cout <<x<<'', ''<<y<<'') and '';
 cout << ''I'm'' << color <<''.
";
}
const Point&
Point::operator=(const Point& rhs)
{
 x = rhs.x; y = rhs.y;
 delete color;
 color = new char[strlen(rhs.color)+1];
 strcpy(color, rhs.color);
}
// This function returns a hidden pointer
// (i.e., a reference) to a temporary Point
// object that will be destroyed upon return
// from the function.
const Point&
Point::operator+(const Point& rhs)
{
 Point temp;
 temp.x = x + rhs.x; temp.y = y + rhs.y;
 delete temp. color;
// Not exactly a good color-mixing scheme!
 temp.color = new char[strlen(color)+strlen(rhs.color)+1];
 sprintf(temp.color, ''%s%s'', color, rhs.color);
 return(temp);
main()}
{
 Point p1(10, 10, ''Blue''),
 Point p2(20, 60, ''Green''),
// The copy constructor is receiving a destroyed
// Point object as an argument since the operator+
// function returns a hidden pointer to the
// automatic variable temp. This variable had its
// destructor called upon exit from operator+.
 Point p3 = p1 + p2;

 p3.print();
}

Another attempt at a solution to this problem is to make the temporary Point object of internal static storage class. An internal static object is not created and destroyed upon entrance/exit from the function; it simply goes in and out of scope. This code will work fine, provided the operator is never used in a nested function call. Of course, one can imagine all sorts of uses for nesting addition operators. The C++ statement x+y+z is one of the more mundane yet valid examples. Because each call to operator+ uses exactly the same temporary Point object, the nested calls will stomp the memory of each other.

Returning an Internal Static Object Reference

#include <iostream.h> 
class Point {
 int x, y;
 char* color;
public:
 Point(int = 0, int = 0, char* = ''Red''),
 ~Point();
 Point(const Point&);
 void print();
 const Point& operator=(const Point&);
 const Point& operator+(const Point&);
};
const Point&
Point::operator+(const Point& rhs)
{
// Note the use of the internal static storage class
// for temp. Each caller to this function reads and
// writes exactly the same Point object.
 static Point temp;
 temp.x = x + rhs.x; temp.y = y + rhs.y;
 delete temp.color;
//Not exactly a good color-mixing scheme!
 temp.color = new char[strlen(color)+strlen(rhs.color)+1];
 sprintf(temp.color, ''%s%s'', color, rhs.color);
 return(temp);
}

Taking a step back to examine the relevant problems of each example, we realize that we need a completely separate Point object upon each invocation of the operator, yet its existence must include the scope of the operator's return value. The logical choice is to dynamically allocate the new Point object from within the overloaded operator and to return a Point reference to it.

Returning a Memory Leaking Dynamically Allocated Object

#include <iostream.h> 
class Point {
 int x, y;
 char* color;
public:
 Point(int = 0, int = 0, char* = ''Red''),
 ~Point();
 Point(const Point&);
 void print();
 const Point& operator=(const Point&);
 const Point& operator+(const Point&);
};
const Point&
Point::operator+(const Point& rhs)
{
 Point *temp = new Point;
 temp->x = x + rhs.x;
 temp->y = y + rhs.y;

// This deletion is necessary due to the nature
// of calling the constructor for Point with
// zero arguments.
 delete temp->color;

// Not exactly a good color-mixing scheme!
temp->color = new char[strlen(color)+
        strlen(rhs.color)+1];
sprintf(temp->color, ''%s%s'', color, rhs.color);
 return(*temp);
}

The C++ developer writing this software tests it extensively (complete with nested calls) and finds that it works flawlessly. It is shipped to numerous clients, some of whom begin complaining of memory leakage problems. Where is this memory leakage? This problem is best illustrated by posing the question, “Where is the temp object, which is dynamically allocated upon each invocation of Point::operator+, cleaned up?” Its destructor is not called automatically since pointers never invoke an object's destructor implicitly. The caller cannot explicitly call the Point object's destructor since the address of the object is not available. Consider the C++ code z = x + y;. How does a user retrieve the address of the temp object?

The answer, of course, is that he or she cannot retrieve the address and, therefore, cannot destroy the temp object. The temp object created upon each invocation of Point::operator+ is leaked from the application and cannot be retrieved.

Our initial premise, that we need to return a reference to a Point object from Point::operator+, was an error. As it turns out, nonmodifying operators must always return an object and not a reference to an object. The object returned is of the automatic storage class.

Correct Method for Leak #7

#include <iostream.h> 

class Point {
 int x, y;
 char* color;
public:
 Point(int = 0, int = 0, char* = ''Red''),
 ~Point();
 Point(const Point&);
 void print();
 const Point& operator=(const Point&);
 const Point operator+(const Point&);
};

void
Point::print()
{
 cout << ''I live at ('';
 cout <<x<<'', ''<<y<<'') and'';
 cout << ''I'm'' << str(color) <<''.
'';
}

const Point
Point::operator+(const Point& rhs)
{
 Point temp;
 temp.x = x + rhs.x; temp.y = y + rhs.y;
 delete temp.color;
//Not exactly a good color-mixing scheme!
 temp.color = new char[strlen(color)+
          strlen(rhs.color)+1];
 sprintf(temp.color, ''%s%s'', color, rhs.color);
 return(temp);
}

main()
{
 Point p1(10, 10, ''Blue''),
 Point p2(20, 60, ''Green''),
 Point p3 = p1 + p2;

 p3.print();
}

Leak #8

Forgetting to make the destructor of a base class virtual. The eighth memory leakage error with which a C++ developer needs to be concerned involves the use of virtual destructors. Developers will often use inheritance to create a taxonomy of classes for the purpose of treating the very different leaf classes as a homogeneous set (e.g., treating the very different apple, banana, and orange objects as a homogeneous collection of fruit). This homogeneous collection of fruit is implemented as an array of fruit pointers, where each fruit pointer points at a particular type of fruit object (i.e., an apple, banana, or orange object). Eventually, the delete function must be called on each of the fruit pointers in order to destroy the fruit type to which it points. The main question is which destructor will be called when the statement, delete basket[i] is invoked at runtime (assume basket is the array of fruit pointers). If the destructor for fruit, namely, Fruit:: ~Fruit(), is not virtual, then each time the delete statement is invoked, it is calling the destructor for fruit. If the destructor for fruit is marked virtual (polymorphic), then the correct destructor for the particular type of fruit is invoked at runtime (i.e., Apple objects call Apple::~Apple(), Banana objects call Banana::~Banana()). If any of the leaf classes (in this example, apples, bananas, and oranges) contain pointers to dynamically allocated space, then a nonvirtual base class destructor (in this example, Fruit::~Fruit) will result in memory leakage, since the destructor for the leaf class is never called (see Figure B.3). The following example illustrates this problem, using Fruit, Apple, and Banana.

Graphical example of memory leakage due to a nonvirtual destructor (memory for "Rome", "Cuba", and "Mac" is leaked off the heap).

Figure B.3. Graphical example of memory leakage due to a nonvirtual destructor (memory for "Rome", "Cuba", and "Mac" is leaked off the heap).

Code Example for Memory Leakage #8

#include <iostream.h> 

class Fruit {
 double weight;
 char* color;
protected:
 Fruit(double, char*);
 Fruit(const Fruit&); public:
//Note the nonpolymorphic destructor
  ~Fruit();
// The correct destructor would look like:
// virtual ~Fruit();
 virtual void print();
};

Fruit::Fruit(double w, char* col)
{
 weight = w;
 color = new char[strlen(col)+1];
 strcpy(color, col);
}

Fruit::Fruit(const Fruit& rhs)
{

 weight = rhs.weight;
 color = new char[strlen(rhs.color)+1];
 strcpy(color, rhs.color);
}

Fruit::~Fruit()
{
 delete color;
}

void
Fruit::print()
{
 cout << ''	Fruit weight:'' << weight << ''
'';
cout << ''	Fruitcolor:'' << color << ''
'';
}

class Apple : public Fruit {
 char* variety;
public:
 Apple(double, char*, char*);
 Apple(const Apple&);
 ~Apple();
 void print ();
};

Apple:: Apple(double w, char* col, char* var) : Fruit(w, col)
{
 variety = new char[strlen(var)+1];
 strcpy(variety, var);
}

Apple::Apple(const Apple& rhs):Fruit(rhs)
{
 variety = new char[strlen(rhs.variety)+1];
 strcpy(variety, rhs.variety);
}

Apple::~Apple ()
{
 delete variety;
}

void
Apple::print()
{
 cout << ''Hi, I'm a'' << variety << ''Apple
'';
 Fruit::print();
}
class Banana : public Fruit {
 char* export;
public:
 Banana(double, char*);
 Banana(const Banana&);
 ~Banana();
Banana::Banana(double w, char* exp) : Fruit(w, ''Yellow'')
{
 export = new char[strlen(exp)+1];
 strcpy(export, exp);
}

Banana:: Banana(const Banana&rhs):Fruit(rhs)
{
 export = new char[strlen(rhs.export)+1];
 strcpy(export, rhs.export);
}

Banana::~Banana()
{
 delete export;
}
void
Banana::print()
{
 cout << ''Hi, I'm a banana from '';
 cout << export << ''
'';
}

main()
{
 Fruit *basket[20];
 int i, num;
 double weight;
 char color[128], variety[128], answer[128];
// The following code allows the user to
// interactively build a fruit basket of size
// 1 through 20.
 cout << ''How many fruit in the basket? '';
 cin >> num;
 if (num < 0 || num > 20) num = 10;
 for (i=0; i<num; i++) {
  cout << ''A)pple or B)anana? '';
  cin >> answer;
  if (answer [0] == 'a' || answer[0] == 'A' ) {
   cout << ''Apple's weight: ''; cin >> weight;
   cout << ''Apple's color: ''; cin >> color;
   cout << ''Apple's variety: ''; cin >> variety;
   basket[i] = new Apple(weight, color, variety);
  }

  else {
   cout << ''Banana's weight: '';
   cin >> weight;
   cout << ''Banana's country: '';
   cin >> variety;
   basket[i] = new Banana (weight, variety);
  }
}
for (i=0; i<num; i++) {
 basket[i]->print();
}

// If Fruit's destructor is not virtual,
// then this code will call Fruit's
// destructor num times. This will leak
// the memory used for the variety string in
// each Apple and the export string in each
// Banana.

 for (i=0; i<num; i++) {
  delete basket[i];
 }
}
..................Content has been hidden....................

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