9.8. Creating And Destroying Objects: new And delete

Time: 00:04:20 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)

 

Review

Please review the following as needed.

  1. Stack And Heap
  2. Automatic vs. local vs. stack variable (see the footnote)
  3. Dynamic Memory: new And delete
  4. Member Selection: Dot And Arrow

Java programs create all objects on the heap, and, with few exceptions, programmers always create them with the new operator. C++ programs can create objects on the stack or the heap, giving programmers more options. The code programmers write to instantiate objects on the heap is similar in both languages, and the abstract views of the objects in memory are the same.

JavaC++
public class Widget
{
    private	int	color = 0xff0000;	// (a)
    private	double	cost = 0.0;

    public Widget(){} 				// (b)

    Widget(int aColor, double aCost)		// (c)
    {
        color = aColor;
        cost = aCost;
    }

    public int getColor() { return color; }	//(e)
}
 
Widget	w1 = new Widget();			// (f)
Widget	w2 = new Widget(0x00ff00, 5.99)		// (g)
class widget
{
    private:
        int	color = 0xff0000;		// (a)
        double	cost = 0.0;

    public:
        widget(){}				// (b)

        widget(int a_color, double a_cost)	// (c)
            : color(a_color), cost(a_cost) {}

        ~widget() { . . . }			// (d)

        int get_color() { return color; }	// (e)
};
widget*	w1 = new widget;			// (f)
widget*	w2 = new widget(0x00ff00, 5.99);	// (g)
Abstract Representation
Two Widget objects and two pointer variables represented by boxes. An arrow runs from the boxes representing pointers w1 and w2 to two different boxes representing the widget objects. Each Widget object stores a unique pair of numbers, the color and the cost.
Instantiating objects with the new operator. Creating single objects on the heap with the new operators is similar in Java and C++. Java's naming conventions are more stringent than C++'s. So, a C++ program could use the same class, function, and argument names appearing in the Java code.
  1. Instance variables (Java) and member variables (C++).
  2. Default constructor.
  3. General constructor.
  4. Destructors are the opposite of constructors - they destroy objects. Programmers typically use them to deallocate heap memory with the delete operator, but they can perform any needed "cleans up" operation. The widget class doesn't have a legitimate task for a constructor (as suggested by the ellipses in the function body), but the example includes one to introduce the syntax. We'll cover destructors in detail in the next chapter.
  5. Getter function (next section).
  6. Instantiates an object on the heap and calls the default constructor to initialize it. In C++, the function call parentheses are optional, so widget* w1 = new widget(); is also correct.
  7. Instantiates an object on the heap and calls a default constructor to initialize it.

When a program creates a single object, the Java and C++ new statements and the way the objects "look" in memory are nearly identical. But this situation changes when the program creates an array objects. The code remains similar, but its meaning and its affect in memory are quite different.

Java C++
Widget w3 = new Widget[n]; widget* w3 = new widget[n];
When a Java program creates an array, e.g., 'new Widget[n],' it creates an array of pointers. The picture illustrates an arrow from w2 to an array of pointers. When a C++ program create an array, e.g., 'new Widget[n],' it creates an array of objects, where each object is initialized with a call to the default constructor. The picture illustrates an arrow from w3 to an array of objects.
(a) (b)
Creating object arrays with the new operator. When we create an array automatically or locally on the stack, we must fully specify the size at compile time - the size must be a compile-time constant, not a variable or more complex expression. But, if we create an array dynamically with the new operator, we can wait until runtime to determine the array's size. These examples assume that n = 3.
  1. The Java statement creates an array of pointers (or, more precisely, references).
  2. The C++ statements create an array of objects, and each is initialized or constructed by the default constructor. The C++ syntax does not allow programmers to call a constructor with arguments when initializing objects created en bloc in an array.

Another striking difference is how the two languages discard unneeded objects. The previous examples allocated memory on the heap with new for objects w1, w2, and w3. The program must deallocate the memory and return it to the heap when the objects are no longer needed. Java provides an automatic garbage collector but C++ does not. There are advantages and disadvantages to both approaches. Java's automatic garbage collector takes much longer to run than does C++'s more precise delete operation, but C++'s explicit deallocation is more error-prone than Java's automatic deallocation.

widget* w1;
widget* w2;
widget* w3;
delete w1;
delete w2;
delete[] w3;
(a) (b)
Deallocating dynamic objects with C++'s delete operator.
  1. All three widget variables are defined as pointers (w1 and w2 in Figure 1, and w3 in Figure 2)
  2. All three widgets are deallocated with the delete operator. w1 and w2 each point to a single object, but, although variable w3 looks like the other two pointers, it points to an array of objects. The brackets following the delete operator signal the deletion of an array of objects to the compiler. This information allows the compiler to generate the code necessary to call the destructor, ~ Widget (), for each object in the array; without the brackets, the compiler only calls the destructor once.

Figure 2 illustrates that creating arrays of objects in a Java program is a multi-step process that first requires creating an array of pointers and then creating each object in the array one at a time. Creating an array of pointers in a C++ program is also possible. Furthermore, making the objects one at a time provides another advantage of dynamic memory over automatic variables. Suppose that a program requires a large array of objects. If the array is too small, the program will not have enough objects to complete its tasks, so programmers tend to overestimate the number of objects the program needs. But creating an overly large array of objects using the syntax of Figure 2(b) can result in two problems:

  1. If we create the array large enough that it can hold the maximum number of the objects that the program might ever need, then there is a risk of wasting some of the objects at the end of the array. If each object is large, the wasted objects mean we also waste a lot of memory.
  2. The program also incurs the unnecessary expense of calling the constructor for every unused object in the array. Similarly, if there is a destructor, it must be called for every object in the array, used or not.

Allocating an array of pointers will also waste memory for any unused pointers, but pointers are relatively small, typically much smaller than large objects. So, allocating objects individually with new as needed, and storing their addresses in an array of pointers, allows the program to create the number of objects necessary. Furthermore, the program doesn't call constructors or destructors when pointers are created or destroyed, so this approach also avoids the wasted constructor and destructor calls for unused objects.

Java C++
Widget	w4 = new Widget[100];
for (int i = 0; i < 3; i++)
	w4[i] = new Widget();
widget*	w4[100];
for (int i = 0; i < 3; i++)
	w4[i] = new widget;
An illustration of how a Java program organizes an array of objects in memory. w4 points to an array of pointers. Each pointer in the array points to an object. An illustration of how a C++ program organizes an array of objects in memory. w4 names an array of pointers, and each element of w4 is a pointer that points to an object. This example initializes each object with a call to the default constructor.
(a) (b)
Creating partially filled arrays of object pointers. Both examples illustrate objects initialized with the default constructor. But programmers can choose any appropriate constructor when creating the objects one at a time.
  1. Java treats arrays as structured objects that are created on the heap with new. The program creates the objects one at a time and saves their addresses in the array. The illustration presents an abstract representation of an array of objects in a Java program.
  2. C++ treats arrays as a primitive type. Figure 2(b) illustrates C++ code that creates an array of objects - not pointers. In contrast, the C++ code highlighted in yellow creates an array of pointers, and the objects are created individually in a for-loop. Although the example calls the default constructor, the program may call any constructor, including one requiring arguments.

Objects stored in arrays are still objects that have features (member variables and functions). The program singles out one object from the array with the index operator, [], and then uses a selection operator (dot or arrow) to access any of the object's features. But sometimes, choosing the correct selection operator takes a little thought.

Automatic Array Dynamic Array Array of Pointers
widget w5[100];
	.
	.
	.
w5[10].get_color();
widget* w6 = new widget[100];
	.
	.
	.
w6[10].get_color();
widget*	w7[100];
for (int i = 0; i < 100; i++)
    w7[i] = new widget(0x0000ff, 10.0);
	. . .
w7[10]->get_color();
(a) (b) (c)
Arrays and feature access. Object features, variables and functions, are accessed with an object variable and a selection operator. C++ has two selection operators, "dot" and "arrow," and which one we use depends on the kind of variable used to manage the object. The rule, "use the arrow operator with pointer variables and the dot operator for all other variables," remains unchanged when the variable is an array element.
  1. The first example illustrates an array defined as an automatic variable created on the stack. Each array element is an object initialized by a constructor. One drawback of the syntax used here is that the default constructor is the only one allowed, and it's called 100 times, once for each object. The index operator selects element or object 10 from the array, and the dot operator further selects the get_color() member function to run.
  2. The second example illustrates an array created on the heap and the address of the array stored in the pointer variable w6. Each element is an object initialized by the default constructor, called once for each object. Although w6 is a pointer, the index operator selects a single element or object from the array, so it is the dot operator that selects the get_color() member function to run.
  3. The final example illustrates an array of pointers defined as an automatic variable on the stack, but this statement does not instantiate any objects. Each object is instantiated or created dynamically on the heap (Figure 4(b)) with a for-loop. This approach has the advantage that programmers can use any available constructor. In this example, the program calls a constructor requiring two arguments 100 times - once for each object created. Finally, indexing an array of pointers results in a single pointer, so the arrow operator is used to access the object's members.