joe_04_04 joe_04_04 - 3 months ago 6
C++ Question

Trying to understand why class destructor is being called after assignment operator

This was a homework assignment from last year that I decided to pop open and look through. I ran through the debugger and noticed the destructor was being called at the end of an assignment operation (overloaded assignment). Can anyone tell me why its being called? I provided the full code so that it can be ran in a compiler (which may make it easier to answer the question), but my question is only about a very small portion of code.

In the driver file, there's a function called

void TestCopyConstructor()
. In this function, there's an assignment operation
X = "0.12345";
. This calls one of the overloaded assignment operator functions
MyFloat MyFloat::operator= (const char *Input)
. This functions runs and at the end, of it, a copy constructor is called MyFloat
MyFloat::MyFloat(const MyFloat & RHS)
. After this function, it returns back to the
void TestCopyConstructor()
, where the destructor is called. I'm trying to understand why this destructor is being called. I realize this is a lot of code, which is why I tried to walk through the specific area I'm confused on, as i wasn't sure how to make a stripped down, working version of this code.

DRIVER:

#include "MyFloatD.cpp" // Name of your class definition. Use pathname
// if file is not in current working directory.
#include <iostream>
#include <iomanip>
#include <ctype.h>
#include <new>
using namespace std;

void DisplayTestingOptions();
void GetChoice(char& Ch);

void TestInputOperator();
void TestAssignment();
void TestCopyConstructor();
void TestComparison();
void TestPlus();
void TestDestructor();

float CPU_Seconds();

//============================== main =================================

int main()
{
char Choice;

do
{

DisplayTestingOptions();

GetChoice(Choice);

switch ( Choice )
{
case '1': TestCopyConstructor();
break;
case '2': TestAssignment();
break;
case '3': TestInputOperator();
break;
case '4': TestPlus();
break;
case '5': TestComparison();
break;
case '6': TestDestructor();
break;
case 'Q': ; // Exit
};
}
while ( Choice != 'Q' );

return 0;
}

/*********************** SpaceBarToContinue **************************

Displays "Spacebar to continue" and returns 1 iff Spacebar pressed.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int SpaceBarToContinue(const char* Message = " Spacebar continues this test")
{
char Ch;

cout << "\n------------------------------------------"
<< Message;
Ch = cin.get();

return ( Ch == ' ' );
}

/*********************** DisplayTestingOptions ************************

Displays a menu of choices.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void DisplayTestingOptions()
{
const char* TAB = " "; // Provides menu spacing

cout << "\n\n";
cout << "\n----------------------------------"
"------------------------------------\n\n";

cout << TAB << "1) Test copy constructor ";

cout << TAB << "2) Test assignment operator \n";

cout << TAB << "3) Test >> input function ";

cout << TAB << "4) Test + operator \n";

cout << TAB << "5) Test == operator ";

cout << TAB << "6) Test destructor \n";

cout << TAB << "Q) Quit program\n\n";

cout << "Choice? ";

}

/*********************** GetChoice ************************************

Reads a char from the keyboard, provides a redirectable echo and
upcases the char.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void GetChoice(char& Ch)
{
cin >> Ch;
Ch = toupper(Ch);

}

/********************* TestInputOperator ******************************

Allows testing of the member functions ">>" of MyFloat.

The insertion operator << is assumed to be correct.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void TestInputOperator()
{
MyFloat X;

cout << "\n\n>>>>>>>>>>>>> Testing input operator >>>>>>>>>>>>>>>>>\n";
do
{
cout << "\nEnter MyFloat ==> ";
cin >> X;

cout << "\nAfter the read, X = '" << X << "'\n\n";
}
while ( SpaceBarToContinue() );

if (cin.peek() != EOF)
cin.ignore(1000, '\n');

}


/********************* TestAssignment *********************************

Allows testing of the member function "=" of MyFloat. If a deep copy
assignment operator has not been written, the code below will give
unexpected output.

This routine assumes that input and output functions for MyFloats
have been written and debugged.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void TestAssignment()
{
MyFloat X(10), Y(10);

cout << "\n\n------------ Testing '=' for MyFloats --------------------\n\n";

X = "0.1234567890";

Y = X; // This must be a deep copy

X = "0.0"; // Or this will change Y!

cout << "\nAfter the assignments, X = \"0.1234567890\", Y = X, and X = 0.0, "
<< "Y = " << Y << endl;

}


/********************* TestPlus ***************************************

Allows testing of the member function operator+. At the present time,
it allows testing only of addition for MyFloats that have the default
length.

NOTE: Calls two of the copy constructors. The copy constructor also
needs to be working correctly for + to work.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void TestPlus()
{
MyFloat X(12), Y, Sum;


cout << "\n\n++++++++++++++++ Testing \"+\" for MyFloat ++++++++++++++++\n"
<< " (Do not use leading zero)\n";
do
{
cout << "\nEnter X ==> ";
cin >> X;
cin.ignore(1000, '\n'); // Discard all chars in input stream.

cout << "\nEnter Y ==> + ";
cin >> Y;

cin.ignore(1000, '\n'); // Discard all chars in input stream.
cout << " ";

for ( int k = 1; k <= X.Digits() + 3 || k <= Y.Digits() + 3 ; ++k )
cout << '-';

cout << "\n ";

Sum = X + Y;

cout << Sum << "\n\n";

}
while ( SpaceBarToContinue() );
}


/********************* TestCopyConstructor ****************************

Allows testing of the copy constructor that is called automatically
under certain circumstances.

Note that that the default constructor and the overloaded "=" operator
must also be working properly.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void TestCopyConstructor()
{
MyFloat X; // Default constructor called

X = "0.12345"; // Overloaded "=" operator called

MyFloat Y = X; // Copy constructor called

X = "0.0";

cout << "\n\n============ Testing copy constructor ================\n\n";
cout << "NOTE: This function also calls default constructor and '='operator. \n\n\n";

cout << "After 'X = 0.0', Y = " << Y << "\nNote that Y should be 0.12345\n";

}

/********************* TestComparison *********************************

Allows testing of the member function operator+. At the present time,
it allows testing only of addition for MyFloats that have the default
length.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void TestComparison()
{
MyFloat A, B, Sum;


cout << "\n\n== == == == == Testing \"== \" for MyFloat == == == == == \n\n";

cout << "MyFloat variables have maximum length of " << A.MaxDigits() << endl;
do
{
cout << "\nEnter A ==> ";
cin >> A;
cout << "\nEnter B ==> ";
cin >> B;

cout << "\n (A == B) is " << ((A == B) ? "TRUE " : "FALSE ") << endl;


}
while ( SpaceBarToContinue() );
}

/********************* TestDestructor **********************************

Tests to see if the class destructor has been correctly written. If not
this program may crash.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void TestDestructor()
{

const unsigned long REPETITIONS = 70000;

for ( unsigned long N = 1; N <= REPETITIONS; ++N )
{
MyFloat X(10000); // Create 1000 digit MyFloat
} // X goes out of scope at each repetition

// If the destructor is not correct, each repetition of the
// loop above will caused some memory leak and the call to
// new below will fail.

int* A = new int[120];

if ( A == NULL )
cout << "\n\nYour memory has leaked away!\n";
else
{
delete [] A;
cout << "\n\n~~~~~~~~~~~~~~ destructor is OK! ~~~~~~~~~~~~~~~\n";
}
SpaceBarToContinue(" spacebar to continue");
}


AND THE CLASS ITSELF:

/******************************************************************
----------------------Class Breakdown----------------------------

Data Members:

-enum (Default Size) sets the size of the MyFloat to 10 by default if no
size is specified

-Char *Number points at array being dynamically allocated in memory

-NumberOfDigits represents the length of the current float

-MaxNumberOfDigits represents the longest length that that particular
object can hold

Member Functions:

-MyFloat(const MyFloat & RHS) copy constructor allows usuer to initialize
new object with another object, by placing it in the parameter list

-MyFloat() is default constructor, initializes objects at MaxSize of 10

-MyFloat(unsigned int Input) standard constructor allows user to create
MyFloat of any size by placing a numerical value in the parameter list

-int Digits() returns size of number currently stored in object

-int MaxDigits() returns the maximum size allowed in current object

-MyFloat operator= (const char *Input) overloaded assignemnt operator
to work with setting an object equal to a literal string (X="0.004")

-MyFloat operator= (const MyFloat &X) overloaded assignent operator to work
with setting an object equal to another object

-int operator== (const MyFloat &x) overloaded comparison operator

-MyFloat operator+ (MyFloat X) overloaded addition operator

-int operator> (MyFloat x) overloaded greater than comparison operator

-friend ostream& operator<< (ostream &Out, const MyFloat & X)
overloaded insertion operator, so user can cout << X

-friend istream& operator>> (istream &In, MyFloat & X)
overloaded extraction operator, so user can cin >> X
********************************************************************/
#include <iostream>
#include <ctype.h>

using namespace std;

class MyFloat
{
enum {DefaultSizeTen=10};
char *Number;

int NumberOfDigits;
int MaxNumberOfDigits;

public:

~MyFloat();

MyFloat(const MyFloat & RHS);
MyFloat();
MyFloat(unsigned int Input);

int Digits();
int MaxDigits();

MyFloat operator= (const char *Input);
MyFloat operator= (const MyFloat &X);

int operator== (const MyFloat &x);

MyFloat operator+ (const MyFloat &X);
int operator> (const MyFloat &x);

friend ostream& operator<< (ostream &Out, const MyFloat & X);
friend istream& operator>> (istream &In, MyFloat & X);

};

/********************* MyFloat Destructor ***************************
Action: Deletes the memory being pointed to by the pointer Number;
Parameters:
IN: none
OUT: none
Returns: none
Preconditions: Number has to be pointing at memory, or else error
occur
*******************************************************************/

MyFloat::~MyFloat()
{
delete Number;
}

/********************* MyFloat Copy Constructor *********************
Action: Initializes an object of MyFloat to be equal to any object being
passed in via parameter list
Parameters:
IN: none
OUT: const MyFloat & RHS - float being getting copied
Returns: none
Preconditions: must be of same data type - MyFloat
*******************************************************************/

MyFloat::MyFloat(const MyFloat & RHS)
{
MaxNumberOfDigits=RHS.MaxNumberOfDigits;
NumberOfDigits=RHS.NumberOfDigits;

Number = new (nothrow) char[RHS.NumberOfDigits+1]; //+1 for overflow

if (Number != NULL)
{
for (int i=0; i<=RHS.NumberOfDigits-1; ++i)
{
Number[i]=RHS.Number[i];
}
}

else
NumberOfDigits=0;
}

/********************* MyFloat Default Constructor *******************
Action: Sets NumberOfDigits to 10 and places 0 in all elements of array
being pointed to by *Number
Parameters:
IN: none
OUT: none
Returns: none
Preconditions: none
*******************************************************************/

MyFloat::MyFloat()
{
MaxNumberOfDigits=DefaultSizeTen;//set MaxNumberOfDigits to default size of 10
NumberOfDigits = 0;

Number = new (nothrow) char[MaxNumberOfDigits+1]; //+1 for overflow

if (Number != NULL)
{
for (int i=0; i<=MaxNumberOfDigits; ++i)
{
Number[i]=0;
}
}
}

/********************* MyFloat Standard Constructor *******************
Action: Sets NumberOfDigits to be equal to whatever user places inside of
the paramter list
Parameters:
IN: unsigned int Input
OUT: none
Returns: none
Preconditions: Input must be int and not negative
*******************************************************************/

MyFloat::MyFloat(unsigned int Input)//Constructor that zeros out Number[] and NumberOfDigits
{
MaxNumberOfDigits=Input;
NumberOfDigits=0;

Number = new (nothrow) char[MaxNumberOfDigits+1];

if (Number != NULL)
{
for (int i=0; i<=MaxNumberOfDigits; ++i)
{
Number[i]=0;
}
}
}

/********************* Digits() ********************************
Action: Simply returns NumberOfDigits to main
Parameters:
IN: none
OUT: none
Returns: returns the NumberOfDigits
Preconditions: none
*******************************************************************/

int MyFloat::Digits()
{
return NumberOfDigits;
}

/********************* MaxDigits() ********************************
Action: Returns MAXDIGIT to main
Parameters:
IN: none
OUT: none
Returns: returns MAXDIGIT
Preconditions: none
*******************************************************************/

int MyFloat::MaxDigits()//Return MAXDIGIT or MaxNumberOfDigits?
{
return MaxNumberOfDigits;
}

/********************* operator=() ********************************
Action: Overloads the assignment operator to allow a string to be stored
in the MyFloat data type, by dynamically allocating memory for a new
array
Parameters:
IN: none
OUT: Const char *Input, which points to the string being assigned to MyFloat
data type
Returns: *this

Preconditions: Assumes incoming string was typed with leading '0' and '.',
these are skipped when storing the string in the MyFloat data type
Number isn't stored if decimal isn't present

*Modified overloaded = function from solution set, since my original
function had some errors
*******************************************************************/

MyFloat MyFloat::operator= (const char *Input)
{
int CounterForInput = 0, CounterForNumber = 0, Length = 0, Temp = 0;

NumberOfDigits = 0;


/* SKIP PAST LEADING WHITESPACE, LEADING 0s, and Decimal */


while ( (isspace(Input[CounterForInput]) || Input[CounterForInput] == '0') && Input[CounterForInput] != 0 ) // Skip blanks and zeros
++CounterForInput;

if ( Input[CounterForInput] != '.' )
return *this; // Error, no decimal point

++CounterForInput; // Move to char after '.'


/* CHECK TO SEE IF POINTER IS POINTING AT AN ARRAY WITH ENOUGH ROOM FOR COPY */


Temp = CounterForInput;

while (isdigit(Input[Temp])) // get length of incoming float without affecting CounterForInput
{
++Length;
++Temp;
}

if (MaxNumberOfDigits<Length)
{
delete Number;
Number = new (nothrow) char[Length+1];

if (Number == NULL)
{
NumberOfDigits = 0;
return *this;
}
}


/* ASSIGN INPUT TO ARRAY BEING POINTED AT BY NUMBER */


while ( CounterForNumber <= MaxNumberOfDigits && isdigit(Input[CounterForInput]) ) // Copy rest of string
Number[CounterForNumber++] = Input[CounterForInput++] - '0';

NumberOfDigits = CounterForNumber;

while ( CounterForNumber < MaxNumberOfDigits ) // Pad with trailing zeros
Number[++CounterForNumber] = 0;

return *this;
}

/********************* operator=() ********************************
Action: Another Overloaded assignment operator to allow a MyFloat to be
copied to another MyFloat, by dynamically allocating memory for a new
array
Parameters:
IN: none
OUT: Const MyFloat &X
Returns: *this
Preconditions: none
*******************************************************************/

MyFloat MyFloat::operator= (const MyFloat &X)
{
if (MaxNumberOfDigits<X.MaxNumberOfDigits)
{
delete Number;

Number = new (nothrow) char [X.MaxNumberOfDigits+1];

if (Number == NULL)
{
NumberOfDigits=0;
return *this;
}
}

NumberOfDigits = X.NumberOfDigits;
MaxNumberOfDigits = X.MaxNumberOfDigits;

for (int i=0; i<=MaxNumberOfDigits; i++)
{
Number[i] = X.Number[i];
}

return *this;
}

/********************* operator==() ********************************
Action: Overloads the comparison operator to compare data members of
two different objects of MyFloat
Parameters:
IN: none
OUT: const MyFloat &x, which contains data members holding one float
Returns: 1 if both MyFloat numbers are equal, 0 if they are different
Preconditions: none
*******************************************************************/

int MyFloat::operator== (const MyFloat &x)
{
int Counter;

if (NumberOfDigits>x.NumberOfDigits)
Counter = NumberOfDigits;

else
Counter = x.NumberOfDigits;

for (int i=0; i<=Counter || Number[i]; ++i)
{
if (x.Number[i]!=Number[i])
return 0;
}
return 1;
}

/********************* operator+() ********************************
Action: Overloads the addition operator to allow it to add two MyFloats
together
Parameters:
IN: none
OUT: MyFloat x, which contains data members holding one float
Returns: MyFloat Storage back to main driver
Preconditions: none
*******************************************************************/

MyFloat MyFloat::operator+ (const MyFloat &X)
{
int Carry=0;
int Integer=0;
int DifferenceInLength=0;
int ForLoopCounter=0;
MyFloat Storage;

if (NumberOfDigits > X.NumberOfDigits)
{
Storage = *this;
//just copy larger float over to storage so it is large enough to hold new number
//new number will use the same ending numbers as the longest number as well

DifferenceInLength = NumberOfDigits - X.NumberOfDigits;
//Get the difference between the lengths of the two numbers, will be used in next for loop

ForLoopCounter=NumberOfDigits - DifferenceInLength;
}

else
{
Storage = X;
DifferenceInLength = X.NumberOfDigits - NumberOfDigits;
ForLoopCounter=X.NumberOfDigits - DifferenceInLength;
}

for (int i=ForLoopCounter-1; i>=0; --i) //- 1 to work with Array style counting
{
Integer = ((X.Number[i]) + (Number[i]) + (Carry));

Carry = 0;

if (Integer>=10)
{
Carry = Integer/10;
Integer %= 10;
}

Storage.Number[i] = Integer;

Integer = 0;
}

if (NumberOfDigits>X.NumberOfDigits)
Storage.NumberOfDigits = (NumberOfDigits);

else
Storage.NumberOfDigits = X.NumberOfDigits;//Storage.NumberOfDigits will be equal to the larger NumberOfDigits of the two floats being added

return Storage;
}

/********************* operator>() ********************************
Action: Overloads the greater than operator to allow two MyFloats to be
compared
Parameters:
IN: none
OUT: const MyFloat &x, which contains data members holding one float
Returns: 0 if false, 1 if true (if calling object is greater than object
being passed in)

Preconditions: none
*******************************************************************/

int MyFloat::operator> (const MyFloat &x)
{
for (int i=0; i<=MaxNumberOfDigits; ++i)
{
if (Number[i]<x.Number[i])
return 0;
else if (Number[i]>x.Number[i])
return 1;
}
return 1;
}

/********************* operator<<() ********************************
Action: Overloads << operator to allow it to work on custom MyFloat
data type
Parameters:
IN: none
OUT: ostream &Out, const MyFloat & x
Returns: Reference to ostream
Preconditions: none
*******************************************************************/

ostream & operator<< (ostream &Out, const MyFloat & X)
{
Out << "0.";

if (X.NumberOfDigits != 0)
{
for (int j=0; j<=X.NumberOfDigits-1; ++j)
{
Out << (int) X.Number[j];
}
}

else
Out << "?";

return Out;
}

/********************* operator>>() ********************************
Action: Reads and stores the float typed in by the user using certain
conditions. Skips all leading whitespace and 0s, then stores any
numbers after that.
Parameters:
IN: none
OUT: istream &In, Myfloat & x
Returns: reference to istream
Preconditions: none
*******************************************************************/

istream & operator>> (istream &In, MyFloat & X)
{
int Counter=0;
char Character;

cin.ignore(); // clear newline from input buffer, my algorithm doesn't
// work if newline is in input buffer when entering function

X.NumberOfDigits=0;

cin.get(Character);

while ((isspace(Character) || Character=='0') && (Character != '\n'))
cin.get(Character);

if (Character != '.')
return In;

cin.get(Character);

while (isdigit(Character) && Counter<=X.MaxNumberOfDigits-1)
{
X.Number[Counter]= Character-'0';
cin.get(Character);
++Counter;
}

cin.putback(Character);//puts back last character stored - newline in this case

X.NumberOfDigits= Counter;

for (; Counter<=X.MaxNumberOfDigits-1; ++Counter)
{
X.Number[Counter]=0;
}

cin.ignore(100, '\n'); // flush any extra numbers in input - mostly for when
// user types in a number longer than array can hold

return In;
}

Answer

A typical assignment operator for class C returns C& - a reference to the object on the left-hand side of the assignment.

Yours, instead, returns a copy of that object, by value. It is that temporary copy that is then immediately destroyed.

Comments