Copy Constructors and Assignment Operators

When we deal with classes and objects, we are suddenly forced to deal with functions that we take for granted with simple types like int and double.

Every class should have at least one constructor.  The simplest one takes no arguments and contains no actual code.  For example, a class called Thing would have the constructor:

	Thing( ) { ; }
The compiler will automatically create this function for you if you don't.  The advantage of you actually typing it in is that you can add code to it later.

Every class should also have a copy constructor and an assignment operator.  These are two functions which ensure that an object's member data gets copied correctly.  Like a default constructor, the compiler will automatically write a copy constructor and assignment operator for each class, if you don't.

Most of the time, those automatically generated functions will work correctly.  However, if you are dealing with any dynamically-allocated data, then you *must* write your own copy constructor and assignment operator.

Copy Constructor

A copy constructor, as the name implies, is a constructor, giving you a new object that is an "identical copy" of another object.  (What is meant by "an identical copy" is up to the designer of the class -- not all data members may need to be copied.)

The general form of a copy constructor takes a constant (i.e., read-only) reference to an object of the same class.  For the Thing class mentioned above, the copy constructor declaration would look like:

	Thing( const Thing & );

Assignment Operator

An assignment operator is a function that gets called when you write an assignment statement, such as a = b;.  Typically, you will assign one object to another object from the same class.  The general format takes a constant reference to an object and returns a reference to an object.  For example:

	Thing & operator=( const Thing & );
The parameter is the object on the right of the equal sign, and the return value is the object on the left.  The assignment statement a = b translates into:
	a.operator=( b );
This return value also allows you to chain assignment statements together, e.g., a = b = c, which translates into:
	a.operator=( b.operator=( c ) );

The difference between a copy constructor and assignment operator is that a copy constructor gives you a brand new object, while an assignment operator modifies an existing object.

Listing 1A.  HockeyTeam class using C-style strings

#if !defined(__HOCKEY_TEAM_H__)
#define __HOCKEY_TEAM_H__

class ostream;		// "Forward class declaration":  so we don't
			//  have to #include <iostream.h>

//
//  Class to model a hockey team
//

class HockeyTeam
{
private:
	char *	itsConference;		// Conference
	char *	itsDivision;		// Division
	char *	itsName;		// Name of team (city)
	int	itsWins;		// Number of games won
	int	itsLosses;		// Number of games lost
	int	itsTies;		// Number of games tied
	int	itsGoalsFor;		// Total goals scored by this team
	int	itsGoalsAgainst;	// Total goals scored against this team
	int	itsPoints;		// Total number of points

public:
	HockeyTeam( const char * theName = 0 );	// "Constructor"
	HockeyTeam( const HockeyTeam & );	// "Copy Constructor"
	~HockeyTeam( );				// "Destructor"

	// "Assignment Operator" 

	HockeyTeam & operator=( const HockeyTeam & );

	// "Accessors"

	const char *	getConference( ) const	 { return itsConference; }
	const char *	getDivision( ) const	 { return itsDivision; }
	const char *	getName( ) const	 { return itsName; }

	int 		getWins( ) const 	 { return itsWins; }
	int 		getLosses( ) const 	 { return itsLosses; }
	int 		getTies( ) const 	 { return itsTies; }
	int 		getGoalsFor( ) const 	 { return itsGoalsFor; }
	int 		getGoalsAgainst( ) const { return itsGoalsAgainst; }
	int 		getPoints( ) const	 { return itsPoints; }

	int		gamesPlayed( ) const
			{ return itsWins + itsTies + itsLosses; }

	// "Mutators"

	void		setConference( const char * theConference );
	void		setDivision( const char * theDivision );
	void		changeName( const char * theName );

	void 		reorganize( const char * theConference, 
				    const char * theDivision );

	void		applyGame( int goalsFor, int goalsAgainst );
	void		applyGame( int goalsFor, int goalsAgainst, bool bOT );
};

//////////////////////////////////////////////////////////////////////////
//  Function:		operator<<
//  Description:	team insertion operator
//  Parameters:		ostr - an output stream, theTeam - a team
//  Returns:		ostr
//////////////////////////////////////////////////////////////////////////
inline ostream &
operator<<( ostream & ostr, const HockeyTeam & theTeam )
{
	ostr << theTeam.getConference( ) << ' ';
	ostr << theTeam.getDivision( ) << ' ';
	ostr << theTeam.getName( ) << ' ';
	ostr << theTeam.getWins( ) << ' ';
	ostr << theTeam.getLosses( ) << ' ';
	ostr << theTeam.getTies( ) << ' ';
	ostr << theTeam.getGoalsFor( ) << ' ';
	ostr << theTeam.getGoalsAgainst( ) << ' ';
	ostr << theTeam.getPoints( );
	return ostr;
}

#endif /* __HOCKEY_TEAM_H__ */
Download this file

Listing 1B.  HockeyTeam class using C-style strings

#include <iostream.h>
#include <string.h>
#include "HockeyTeam3A.h"

HockeyTeam::HockeyTeam( const char * theName ) :
	// "Memberwise initialization"
	itsConference( 0 ),
	itsDivision( 0 ),
	itsName( 0 ),
	itsWins( 0 ),
	itsLosses( 0 ),
	itsTies( 0 ),
	itsGoalsFor( 0 ),
	itsGoalsAgainst( 0 ),
	itsPoints( 0 )
{
#	if defined(DEBUG)
	cout << "HockeyTeam constructor called" << endl;
#	endif

	if ( theName != 0 )
	{
		itsName = new char [ strlen( theName ) + 1 ];
		strcpy( itsName, theName );
	}
}

// Copy constructor
HockeyTeam::HockeyTeam( const HockeyTeam & theTeam ) :
	// "Memberwise initialization"
	itsConference( 0 ),
	itsDivision( 0 ),
	itsName( 0 ),
	itsWins( theTeam.itsWins ),
	itsLosses( theTeam.itsLosses ),
	itsTies( theTeam.itsTies ),
	itsGoalsFor( theTeam.itsGoalsFor ),
	itsGoalsAgainst( theTeam.itsGoalsAgainst ),
	itsPoints( theTeam.itsPoints )
{
#	if defined(DEBUG)
	cout << "HockeyTeam copy constructor called" << endl;
#	endif

	if ( theTeam.itsConference != 0 )
	{
		itsConference = new char [ strlen( theTeam.itsConference ) + 1 ];
		strcpy( itsConference, theTeam.itsConference );
	}
	if ( theTeam.itsDivision != 0 )
	{
		itsDivision = new char [ strlen( theTeam.itsDivision ) + 1 ];
		strcpy( itsDivision, theTeam.itsDivision );
	}
	if ( theTeam.itsName != 0 )
	{
		itsName = new char [ strlen( theTeam.itsName ) + 1 ];
		strcpy( itsName, theTeam.itsName );
	}
}

HockeyTeam::~HockeyTeam( )
{
#	if defined(DEBUG)
	cout << "HockeyTeam destructor called" << endl;
#	endif

	delete [] itsConference;
	delete [] itsDivision;
	delete [] itsName;
}

// Assignment operator
HockeyTeam &
HockeyTeam::operator=( const HockeyTeam & theTeam )
{
#	if defined(DEBUG)
	cout << "HockeyTeam assignment operator called" << endl;
#	endif

	// prevent self-assignment
	if ( this != &theTeam )
	{
		delete [] itsConference;
		itsConference = 0;
		if ( theTeam.itsConference != 0 )
		{
			itsConference = new char [
				strlen( theTeam.itsConference ) + 1 ];

			strcpy( itsConference, theTeam.itsConference );
		}

		delete [] itsDivision;
		itsDivision = 0;
		if ( theTeam.itsDivision != 0 )
		{
			itsDivision = new char [
				strlen( theTeam.itsDivision ) + 1 ];

			strcpy( itsDivision, theTeam.itsDivision );
		}

		delete [] itsName;
		itsName = 0;
		if ( theTeam.itsName != 0 )
		{
			itsName = new char [ strlen( theTeam.itsName ) + 1 ];
			strcpy( itsName, theTeam.itsName );
		}

		itsWins = theTeam.itsWins;
		itsLosses = theTeam.itsLosses;
		itsTies = theTeam.itsTies;
		itsGoalsFor = theTeam.itsGoalsFor;
		itsGoalsAgainst = theTeam.itsGoalsAgainst;
		itsPoints = theTeam.itsPoints;
	}
	return *this;
}

void		
HockeyTeam::setConference( const char * theConference )
{
	if ( theConference != 0 )
	{
		delete [] itsConference;
		itsConference = new char [ strlen( theConference ) + 1 ];
		strcpy( itsConference, theConference );
	}
}

void		
HockeyTeam::setDivision( const char * theDivision )
{
	if ( theDivision != 0 )
	{
		delete [] itsDivision;
		itsDivision = new char [ strlen( theDivision ) + 1 ];
		strcpy( itsDivision, theDivision );
	}
}

void		
HockeyTeam::changeName( const char * theName )
{
	if ( theName != 0 )
	{
		delete [] itsName;
		itsName = new char [ strlen( theName ) + 1 ];
		strcpy( itsName, theName );
	}
}

void 		
HockeyTeam::reorganize( const char * theConference, const char * theDivision )
{
	if ( ( theConference != 0 ) && ( theDivision != 0 ) )
	{
		setConference( theConference );
		setDivision( theDivision );
	}
}

void		
HockeyTeam::applyGame( int goalsFor, int goalsAgainst )
{
	applyGame( goalsFor, goalsAgainst, false );
}

void		
HockeyTeam::applyGame( int goalsFor, int goalsAgainst, bool bOvertime )
{
	itsGoalsFor += goalsFor;
	itsGoalsAgainst += goalsAgainst;

	if ( goalsFor > goalsAgainst )
	{
		itsPoints += 2;
		itsWins += 1;
	}
	else if ( goalsFor < goalsAgainst )
	{
		itsLosses += 1;
		if ( bOvertime )
		{
			itsPoints += 1;
		}
	}
	else	// must be == (a tie)
	{
		itsTies += 1;
		itsPoints += 1;
	}
}
Download this file

Listing 2A.  HockeyTeam class using C++ string objects

#if !defined(__HOCKEY_TEAM_H__)
#define __HOCKEY_TEAM_H__

#include <string>

using namespace std;

//
//  Class to model a hockey team
//

class HockeyTeam
{
private:
	string	itsConference;		// Conference
	string	itsDivision;		// Division
	string	itsName;		// Name of team (city)
	int	itsWins;		// Number of games won
	int	itsLosses;		// Number of games lost
	int	itsTies;		// Number of games tied
	int	itsGoalsFor;		// Total goals scored by this team
	int	itsGoalsAgainst;	// Total goals scored against this team
	int	itsPoints;		// Total number of points

public:
	HockeyTeam( const char * theName = 0 );	// "Default Constructor"
	HockeyTeam( const HockeyTeam & );	// "Copy Constructor"
	~HockeyTeam( );				// "Destructor"

	// "Assignment Operator" 

	HockeyTeam & operator=( const HockeyTeam & );

	// "Accessors"

	const char *	getConference( ) const	
				{ return itsConference.data( ); }
	const char *	getDivision( ) const	
				{ return itsDivision.data( ); }
	const char *	getName( ) const	 
				{ return itsName.data( ); }

	int 		getWins( ) const 	 { return itsWins; }
	int 		getLosses( ) const 	 { return itsLosses; }
	int 		getTies( ) const 	 { return itsTies; }
	int 		getGoalsFor( ) const 	 { return itsGoalsFor; }
	int 		getGoalsAgainst( ) const { return itsGoalsAgainst; }
	int 		getPoints( ) const	 { return itsPoints; }

	int		gamesPlayed( ) const
			{ return itsWins + itsTies + itsLosses; }

	// "Mutators"

	void		setConference( const char * theConference );
	void		setDivision( const char * theDivision );
	void		changeName( const char * theName );

	void 		reorganize( const char * theConference, 
				    const char * theDivision );

	void		applyGame( int goalsFor, int goalsAgainst );
	void		applyGame( int goalsFor, int goalsAgainst, bool bOT );
};

//////////////////////////////////////////////////////////////////////////
//  Function:		operator<<
//  Description:	team insertion operator
//  Parameters:		ostr - an output stream, theTeam - a team
//  Returns:		ostr
//////////////////////////////////////////////////////////////////////////
inline ostream &
operator<<( ostream & ostr, const HockeyTeam & theTeam )
{
	ostr << theTeam.getConference( ) << ' ';
	ostr << theTeam.getDivision( ) << ' ';
	ostr << theTeam.getName( ) << ' ';
	ostr << theTeam.getWins( ) << ' ';
	ostr << theTeam.getLosses( ) << ' ';
	ostr << theTeam.getTies( ) << ' ';
	ostr << theTeam.getGoalsFor( ) << ' ';
	ostr << theTeam.getGoalsAgainst( ) << ' ';
	ostr << theTeam.getPoints( );
	return ostr;
}

#endif /* __HOCKEY_TEAM_H__ */
Download this file

Listing 2B.  HockeyTeam class using C++ string objects

#include "HockeyTeam3B.h"
#include <iostream>
#include <string>

HockeyTeam::HockeyTeam( const char * theName ) :
	// "Memberwise initialization"
	itsConference( ),
	itsDivision( ),
	itsName( ),
	itsWins( 0 ),
	itsLosses( 0 ),
	itsTies( 0 ),
	itsGoalsFor( 0 ),
	itsGoalsAgainst( 0 ),
	itsPoints( 0 )
{
	changeName( theName );

#	if defined(DEBUG)
	cout << "HockeyTeam constructor called (" << itsName << ")" << endl;
	cout << "this = " << reinterpret_cast<unsigned long>( this ) << endl;
#	endif

}

HockeyTeam::HockeyTeam( const HockeyTeam & theTeam ) :
	// "Memberwise initialization"
	itsConference( theTeam.itsConference ),
	itsDivision( theTeam.itsDivision ),
	itsName( theTeam.itsName ),
	itsWins( theTeam.itsWins ),
	itsLosses( theTeam.itsLosses ),
	itsTies( theTeam.itsTies ),
	itsGoalsFor( theTeam.itsGoalsFor ),
	itsGoalsAgainst( theTeam.itsGoalsAgainst ),
	itsPoints( theTeam.itsPoints )
{
#	if defined(DEBUG)
	cout << "HockeyTeam copy constructor called" << endl;
	cout << "this = " << reinterpret_cast<unsigned long>( this ) << endl;
	cout << "theTeam = " << reinterpret_cast<unsigned long>( &theTeam )
	     << endl;
#	endif

	;
}

HockeyTeam::~HockeyTeam( )
{

#	if defined(DEBUG)
	cout << "HockeyTeam destructor called (" << itsName << ")" << endl;
	cout << "this = " << reinterpret_cast<unsigned long>( this ) << endl;
#	endif

}

HockeyTeam &
HockeyTeam::operator=( const HockeyTeam & theTeam )
{
#	if defined(DEBUG)
	cout << "HockeyTeam assignment operator called" << endl;
	cout << "this = " << reinterpret_cast<unsigned long>( this ) << endl;
	cout << "theTeam = " << reinterpret_cast<unsigned long>( &theTeam )
	     << endl;
#	endif

	// prevent self-assignment
	if ( this != &theTeam )
	{
		itsConference = theTeam.itsConference;
		itsDivision = theTeam.itsDivision;
		itsName = theTeam.itsName;
		itsWins = theTeam.itsWins;
		itsLosses = theTeam.itsLosses;
		itsTies = theTeam.itsTies;
		itsGoalsFor = theTeam.itsGoalsFor;
		itsGoalsAgainst = theTeam.itsGoalsAgainst;
		itsPoints = theTeam.itsPoints;
	}
	return *this;
}

void		
HockeyTeam::setConference( const char * theConference )
{
	if ( theConference != 0 )
	{
		itsConference = theConference;
	}
}

void		
HockeyTeam::setDivision( const char * theDivision )
{
	if ( theDivision != 0 )
	{
		itsDivision = theDivision;
	}
}

void		
HockeyTeam::changeName( const char * theName )
{
	if ( theName != 0 )
	{
		itsName = theName;
	}
}

void 		
HockeyTeam::reorganize( const char * theConference, const char * theDivision )
{
	if ( ( theConference != 0 ) && ( theDivision != 0 ) )
	{
		setConference( theConference );
		setDivision( theDivision );
	}
}

void		
HockeyTeam::applyGame( int goalsFor, int goalsAgainst )
{
	applyGame( goalsFor, goalsAgainst, false );
}

void		
HockeyTeam::applyGame( int goalsFor, int goalsAgainst, bool bOvertime )
{
	itsGoalsFor += goalsFor;
	itsGoalsAgainst += goalsAgainst;

	if ( goalsFor > goalsAgainst )
	{
		itsPoints += 2;
		itsWins += 1;
	}
	else if ( goalsFor < goalsAgainst )
	{
		itsLosses += 1;
		if ( bOvertime )
		{
			itsPoints += 1;
		}
	}
	else	// must be == (a tie)
	{
		itsTies += 1;
		itsPoints += 1;
	}
}
Download this file

Listing 3.  Test program HockeyTeam classes

// ...

// Copy constructor automatically called here, because we are passing
//  a HockeyTeam object by value
void
display1( HockeyTeam theTeam )
{
	cout << "In " << theTeam.gamesPlayed( ) << " games: " << endl;
	cout << theTeam << endl << endl;
}

// Copy constructor not called here, because we are passing
//  a HockeyTeam object by reference
void
display2( const HockeyTeam & theTeam )
{
	cout << "In " << theTeam.gamesPlayed( ) << " games: " << endl;
	cout << theTeam << endl << endl;
}

// Copy constructor not called here, because we are passing
//  a HockeyTeam object by pointer (another form of by reference)
void
display3( const HockeyTeam * theTeam )
{
	cout << "In " << theTeam->gamesPlayed( ) << " games: " << endl;
	cout << *theTeam << endl << endl;
}

int
main( )
{

	// Constructor called here with the argument "Toronto Maple Leafs"
	HockeyTeam leafs( "Toronto Maple Leafs" );

	// Constructor called here with the argument 0 (null pointer), which
	// is automatically filled in by the compiler
	HockeyTeam toronto;

	leafs.reorganize( "Eastern", "Northeast" );

	leafs.applyGame( 4, 3 );		// 4 - 3 regulation time win
	leafs.applyGame( 4, 5, true );		// 5 - 4 overtime loss 

	display1( leafs );

	// Assignment operator called here.  Formally, it is:
	//   toronto.operator=( leafs );
	toronto = leafs;
	display2( toronto );


	// Pass a pointer to the toronto object.  To make a pointer, we
	//   take the address of the object.
	display3( &toronto );

	return 0;
}

Download the test program for Listings 1A and 1B
Download the test program for Listings 2A and 2B


Back to the COMP435 page