Introduction to Programming and C++

Contents Previous Topic Next Topic

When a variable assigned to one class is assigned to another, by default, the member variables are copied over.

For example,

#include <iostream>
using namespace std;

class Box {
	private:
		int value;

	public:
		Box( int );
		int getValue() const;
		void setValue( int );
};

Box::Box( int v ):value( v ) {
	// empty constructor
}

int Box::getValue() const {
	return value;
}

void Box::setValue( int v ) {
	value = v;
}

int main() {
	Box a( 1 );
	Box b( 23 );

	cout << "The initial value of b is " << b.getValue() << endl;

	b = a;

	cout << "The value of b after copying is " << b.getValue() << endl;

	a.setValue( 456 );

	cout << "The value of b remains " << b.getValue()
	     << " after modifying a" << endl;

	return 0;
}

This is the same if you were to use pointers. If, in the last example, you substitute the main function with:

int main() {
	Box a( 1 );
	Box * b = new Box( 23 );     // request 4 bytes

	cout << "The initial value of b is " << b -> getValue() << endl;

	*b = a;

	cout << "The value of b after copying is " << b -> getValue() << endl;

	a.setValue( 456 );

	cout << "The value of b remains " << b -> getValue()
	     << " after modifying a" << endl;

	a = *b;

	cout << "The value of a after a = *b; is " << a.getValue() << endl;

	b -> setValue( 7890 );

	cout << "The value of a remains " << a.getValue()
	     << " after modifying b" << endl;

	delete b;      // return memory to the OS

	return 0;
}

Non-Default Assignment

When you are making a copy of a class, this may be what you want, however, suppose you don't want to simply copy over the values verbatim. Perhaps there is a unique field which you don't want changed as a result of a copy. This is demonstrated in the next program where each object has two values, an identifier and a value. If you were to use the default assignment, the both values would be copied, however, by overloading the assignment operator, we can ensure that only one value is copied.

#include <iostream>
using namespace std;

class IDBox {
	private:
		int id;
		int value;

	public:
		IDBox( int, int );

		IDBox & operator = ( const IDBox & );

		int getID() const;
		int getValue() const;
		void setValue( int );
};

IDBox::IDBox( int i, int v ):id( i ), value( v ) {
	// empty constructor
}

IDBox & IDBox::operator = ( const IDBox & rhs ) {
	// do not copy the ID
	value = rhs.value;

	return *this;
}

int IDBox::getID() const {
	return id;
}

int IDBox::getValue() const {
	return value;
}

void IDBox::setValue( int v ) {
	value = v;
}

int main() {
	IDBox a( 1, 45 );
	IDBox b( 2, 678 );

	cout << "The initial id and value of b are "
	     << b.getID() << ", " << b.getValue() << endl;

	b = a;

	cout << "After the assignment, the id and value of b are "
	     << b.getID() << ", " << b.getValue() << endl;

	return 0;
}

Let's examine the assignment operator more closely:

IDBox & IDBox::operator = ( const IDBox & rhs ) {
	// do not copy the ID
	value = rhs.value;

	return *this;
}

First, the argument is const IDBox & rhs. The variable name is rhs, for right-hand side (the right-hand side of the assignment). The modifier is const IDBox &, which indicates that:

  • the right-hand side is passed by reference to this function (a copy is not made), and
  • the object cannot be modified in this function (because the argument is declared to be const.

If you were to attempt to assign, for example rhs.value = 4;, this would generate a compile-time error. In general, if something appears on the right-hand side of an assignment, you do not expect it to change.

Next, because the operator is being defined outside the class member declaration, to associate the operator with the class, the operator = must be preceded by IDBox::.

Inside the body of the function, I explicitly put a statement that the ID should not be copied (for anyone else reading the code). Next, I assign value the value of the value member variable of rhs.

The return type is IDBox &. We want to return this object, however, this is a pointer to the current object, and therefore we must return *this (the actual object being pointed to by this). The reason for a return-by-reference is two-fold: we want to return something so that a = b = c; works, however, we don't want to make yet-another copy of b. If you substitute the int main() function in the previous example, you will see how a = b = c; works.

int main() {
	IDBox a( 1, 45 );
	IDBox b( 2, 678 );
	IDBox c( 3, 9000 );

	cout << "The initial id and value of a are "
	     << a.getID() << ", " << a.getValue() << endl;

	cout << "The initial id and value of b are "
	     << b.getID() << ", " << b.getValue() << endl;

	cout << "The initial id and value of c are "
	     << c.getID() << ", " << c.getValue() << endl;

	a = b = c;

	cout << "After the assignment, the id and value of a are "
	     << a.getID() << ", " << a.getValue() << endl;

	cout << "After the assignment, the id and value of b are "
	     << b.getID() << ", " << b.getValue() << endl;

	return 0;
}

Assignment and the Destructor

If a class does not use the default destructor, this would indicate that before an assignment is made that the current object must be cleared. In this case, it is usually a good idea to define a private function void clear() which is called both by the destructor and operator =. This is demonstrated in the next code fragment:

class ClassName {
	private:
		// ...
		void clear();

	public:
		~ClassName();      // non-default destructor
		ClassName & operator = ( const ClassName & );
		// ...
};

void ClassName::clear() {
	// clean up the object
}

ClassName::~ClassName() {
	this -> clear();
}

ClassName & ClassName::operator = ( const ClassName & rhs ) {
	this -> clear();

	// make the appropriate copy of rhs to this

	return * this;
}

Contents Previous Topic Top Next Topic