• No results found

3 • Linked Stacks with Safeguards 133

Programming Project 4

Section 4. 3 • Linked Stacks with Safeguards 133

Stack outer_stack; for(inti = 0; i<1000000; i++){ Stack inner_stack; inner_stack.push(some_data); inner_stack = outer_stack; }

The statementinner_stack = outer_stackcauses a serious problem for ourStackim- plementation. C++ carries out the resulting assignment by copying the data mem- berouter_stack.top_node. This copying overwrites pointerinner_stack.top_node, so the contents ofinner_stack are lost. As we illustrate in Figure 4.12, in every discarded memory

iteration of the loop, the previous inner stack data becomes garbage. The blame for the resulting buildup of garbage rests firmly with ourStackimplementation. As before, no problem occurs when the client uses a contiguous stack implementation.

outer_stack. top_node

inner_stack. top_node some_data

Lost data

X

Figure 4.12. The application of bitwise copy to aStack

This figure also shows that the assignment operator has another undesired consequence. After the use of the operator, the two stack objects share their nodes. alias problem:

dangling pointers Hence, at the end of each iteration of the loop, any application of aStackdestructor oninner_stackwill result in the deletion of the outer stack. Worse still, such a dele- tion would leave the pointerouter_stack.top_nodeaddressing what has become a random memory location.

The problems caused by using the assignment operator on a linked stack arise because it copies references rather than values: We summarize this situation by saying that Stackassignment hasreference semantics. In contrast, when the as- reference semantics

signment operator copies the data in a structure, we shall say that it has value semantics. In our linkedStackimplementation, either we must attach documen- value semantics

tation to warn clients that assignment has reference semantics, or we must make the C++ compiler treat assignment differently.

In C++, we implement special methods, known asoverloaded assignment op- eratorsto redefine the effect of assignment. Whenever the C++ compiler translates overloaded operators

an assignment expression of the formx = y, it first checks whether the class ofx

compiler translate the assignment as a bitwise copy of data members. Thus, to provide value semantics forStackassignment, we should overload assignment for overloaded assignment

ourStackclass.

There are several options for the declaration and implementation of this over- loaded operator. A simple approach is to supply a prototype withvoidreturn type:

96

voidStack:: operator= (constStack&original);

This declares aStackmethod calledoperator=, the overloaded assignment oper- ator, that can be invoked with the member selection operator in the usual way.

x.operator= (y);

Alternatively, the method can be invoked with the much more natural and conve- nient operator syntax:

x = y;

By looking at the type(s) of its operands, the C++ compiler can tell that it should use the overloaded operator rather than the usual assignment. We obtain operator syntax by omitting the period denoting member selection, the keywordoperator, operator syntax

and the parentheses from the ordinary method invocation.

The implementation of the overloaded assignment operator for ourStackclass proves to be quite tricky.

First, we must make a copy of the data stacked in the calling parameter.

Next, we must clear out any data already in theStackobject being assigned to.

Finally, we must move the newly copied data to theStackobject.

97

voidStack:: operator= (constStack&original) // Overload assignment /*Post: The Stack is reset as a copy of Stack original.*/

{

Node*new_top,*new_copy,*original_node = original.top_node;

if(original_node == NULL) new_top =NULL;

else{ // Duplicate the linked nodes

new_copy = new_top =newNode(original_node->entry);

while(original_node->next!=NULL){ original_node = original_node->next;

new_copy->next =newNode(original_node->entry);

new_copy = new_copy->next; }

}

while(!empty( )) // Clean out old Stack entries

pop( );

top_node = new_top; // and replace them with new entries.

Section 4.3 Linked Stacks with Safeguards

135

Note that, in the implementation, we do need topopall of the existing entries out of theStackobject whose value we are assigning. As a precaution, we first make a copy of theStackparameter and then repeatedly apply the methodpop. In this way, we ensure that our assignment operator does not lose objects in assignments such asx = x.

Although our overloaded assignment operator does succeed in giving Stack

remaining defect:

multiple assignment assignment value semantics, it still has one defect: A client cannot use the result of an assignment in an expression such asfist_stack = second_stack = third_stack. A very thorough implementation would return a reference of typeStack&to allow clients to write such an expression.

4.3.3 The Copy Constructor

One final insecurity that can arise with linked structures occurs when the C++ compiler calls for a copy of an object. For example, objects need to be copied when an argument is passed to a function by value. In C++, the default copy operation copies each data member of a class. Just as illustrated in Figure 4.12, the default copy operation on a linkedStackleads to a sharing of data between objects. In other words, the default copy operation on a linkedStackhas reference semantics. This allows a malicious client to declare and run a function whose sole purpose is to destroy linkedStackobjects:

98

voiddestroy_the_stack (Stack copy)

{ } intmain( ) { Stack vital_data; destroy_the_stack(vital_data); }

In this code, a copy of theStack vital_datais passed to the function. TheStack copy

shares its nodes with theStack vital_data, and therefore when aStackdestructor is applied tocopy, at the end of the function,vital_datais also destroyed.

Again, C++ provides a tool to fix this particular problem. Indeed, if we include copy constructor

a copy constructoras a member of our Stackclass, our copy constructor will be invoked whenever the compiler needs to copyStackobjects. We can thus ensure

thatStackobjects are copied using value semantics.

For any class, a standard way to declare acopy constructoris as a constructor with one argument that is declared as a constant reference to an object of the class. Hence, aStackcopy constructor would normally have the following prototype:

Stack::Stack(constStack&original);

In our implementation of this constructor, we first deal with the case of copying an emptyStack. We then copy the first node, after which we run a loop to copy all of the other nodes.

99 Stack::Stack(constStack&original) // copy constructor

/*Post: The Stack is initialized as a copy of Stack original.*/ {

Node*new_copy,*original_node = original.top_node;

if(original_node == NULL) top_node =NULL;

else{ // Duplicate the linked nodes.

top_node = new_copy =newNode(original_node->entry);

while(original_node->next!=NULL){ original_node = original_node->next;

new_copy->next =newNode(original_node->entry);

new_copy = new_copy->next; }

} }

This code is similar to our implementation of the overloaded assignment operator. However, in this case, since we are creating a newStackobject, we do not need to remove any existing stack entries.

In general, for every linked class, either we should include a copy constructor, or we should provide documentation to warn clients that objects are copied with reference semantics.

4.3.4 The Modified Linked-Stack Specification

We close this section by giving an updated specification for a linked stack. In this specification we include all of our proposed safety features.

100

classStack{ public:

// Standard Stack methods

Stack( );

boolempty( )const;

Error_code push(constStack_entry&item);

Error_code pop( );

Error_code top(Stack_entry&item)const; // Safety features for linked structures

Stack( );

Stack(constStack&original);

void operator= (constStack&original); protected:

Node*top_node;

};

Exercises 4.3

E1. Suppose thatx,y, andzareStackobjects. Explain why the overloaded assign- ment operator ofSection 4.3.2cannot be used in an expression such asx = y = z. Modify the prototype and implementation of the overloaded assignment op- erator so that this expression becomes valid.