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