Page 1 of 1

Overloading Operators Rate Topic: ****- 1 Votes

#1 KYA  Icon User is offline

  • g++ jameson.cpp -o beverage
  • member icon

Reputation: 3101
  • View blog
  • Posts: 19,141
  • Joined: 14-September 07

Posted 23 September 2007 - 11:25 PM

Operator Overloading

What is it? It is the coder’s ability to add additional abilities to operators such as +, - *, %, etc… Here is a complete list of operators that can be overloaded in C++:

 * / + - % ^ & | ! , = < > <= >= ++ -- << >> == != && || *= /= %= ^= &= |= += -= <<= >>= -> ->* [] () new delete


These operators cannot be overloaded:

 ., .*, ::, ?:, and sizeof 


Below is an example of using an increment operator without overloading it

 #include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	void Increment() {++itsVal;}

private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::~Counter()
{}

int main()
{
	Counter i; //declares a counter object'i'
	cout << "The value of i is " << i.GetItsVal() << endl;
	i.Increment();
	cout << "The value of i is " << i.GetItsVal() << endl;
	return 0;
}
}


Output: The value of i is 0
The value of 1 is 1

Now this works; it allows the class to be incremented but it does not truly overload the ++ operator yet. Plus this method can eventually get too cumbersome to use. So how does one overload the prefix increment operator?

Default syntax for overloading an operator:
returnType operator op()


example:

void operator++()


This sample code shows how to overload the ++ operator:

#include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	void Increment() {++itsVal;}
	void operator++() {++itsVal;}

private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::~Counter()
{}

int main()
{
	Counter i; //declares a counter object'i'
	cout << "The value of i is " << i.GetItsVal() << endl;
	i.Increment();
	cout << "The value of i is " << i.GetItsVal() << endl;
	++i;
	cout << "The value of i is " << i.GetItsVal() << endl;
	return 0;
}


Output: The value of i is 0
The value of i is 1
The value of i is 2

In the code, the operator ++ is overloaded. Albeit simple, it simply increments the Counter object's private variable itsVal. The use of this operator being overloaded brings it closer to the use of increment operators when used on an integer, etc...

Now say for example, if you wanted to assign a new Counter object 'a' to the value that 'i' currently has like this:

 Counter a = ++i; 


This will fail. The code intends to assign the value of 'i' to a new Counter object 'a'. The program's built in copy constructor can handle the assignment, but the current overloaded operator function is void, it is not set to return anything, much less a Counter object. One possible solution is to create a temporary object and return that:

#include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	void Increment() {++itsVal;}
	Counter operator++();

private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::~Counter()
{}

Counter Counter::operator ++()
{
	++itsVal;
	Counter temp; //temp counter object created here
	temp.SetItsVal(itsVal);
	return temp;
}

int main()
{
	Counter i; //declares a counter object'i'
	cout << "The value of i is " << i.GetItsVal() << endl;
	i.Increment();
	cout << "The value of i is " << i.GetItsVal() << endl;
	++i;
	cout << "The value of i is " << i.GetItsVal() << endl;
	Counter a = ++i;
	cout << "The value of a: " << a.GetItsVal();
	cout << " and i: " << i.GetItsVal() << endl;
	return 0;
}


Output: The value of i is 0
The value of i is 1
The value of i is 2
The value of a: 3 and i: 3

The operator++ function now creates a temp object and can be used when transferring data from one class object to another. The temp value that is returned is immediately assigned to a. Since a temp object must be created and later destroyed, this way of accomplishing the problem will eventually cause problems as well, way too much overhead and potentially a very expensive operation.

To make this use less memory, we will use the ‘this’ pointer:

#include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	void Increment() {++itsVal;}
	const Counter& operator++();

private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::~Counter()
{}

const Counter& Counter::operator ++()
{
	++itsVal;
	return *this;
}

int main()
{
	Counter i; //declares a counter object'i'
	cout << "The value of i is " << i.GetItsVal() << endl;
	i.Increment();
	cout << "The value of i is " << i.GetItsVal() << endl;
	++i;
	cout << "The value of i is " << i.GetItsVal() << endl;
	Counter a = ++i;
	cout << "The value of a: " << a.GetItsVal();
	cout << " and i: " << i.GetItsVal() << endl;
	return 0;
}


Output: The value of i is 0
The value of i is 1
The value of i is 2
The value of a: 3 and i: 3

Using ‘this’ has made the code cleaner and more efficient.

Now, we have gone over overloading the prefix operator, but what about the postfix one? Instead of incrementing and then fetching, we must now fetch and then increment. For the initial code for this we will be creating a temporary object again, why? Consider this following segment:

 a = x++;


If x has a value of 6, 'a' is assigned that value and x then becomes 7. If x is an object in this instance, the postfix increment operator needs to stash x's original value away, increment, then assign the new value to a. However this cannot be passed by reference, because immediately after it's done the temp object will go out of scope:

 #include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	void Increment() {++itsVal;}
	const Counter& operator++(); //prefix
	const Counter operator++(int); //postfix

private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::~Counter()
{}

const Counter& Counter::operator ++()
{
	++itsVal;
	return *this;
}

const Counter Counter::operator ++(int theFlag)
{
	Counter temp(*this);
	++itsVal;
	return temp;
}

int main()
{
	Counter i; //declares a counter object'i'
	cout << "The value of i is " << i.GetItsVal() << endl;
	i.Increment();
	cout << "The value of i is " << i.GetItsVal() << endl;
	++i;
	cout << "The value of i is " << i.GetItsVal() << endl;
	Counter a = ++i;
	cout << "The value of a: " << a.GetItsVal();
	cout << " and i: " << i.GetItsVal() << endl;
	a = i++;
	cout << "The value of a: " << a.GetItsVal();
	cout << " and i: " << i.GetItsVal() << endl;
	return 0;
}


Output: The value of i is 0
The value of i is 1
The value of i is 2
The value of a: 3 and i: 3
The value of a: 3 and i: 4

The parameter passed into the postfix operator is used simply to differentiate and that the compiler knows which one to use, the actual value of 'theFlag' is never used.

Now to overloading binary operators:

Binary Operators

Unlike increments/decrements, operators such as addition (+), multiplication (*), division (/), and subtraction (-) will have to deal with two numbers, two objects, two of anything etc... to perform their function. Having this in mind, overloading their function will be different then the examples above:

#include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	Counter(int initialValue);
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	void Increment() {++itsVal;}
	Counter Add(const Counter &);

private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::Counter(int initialValue):
itsVal(initialValue)
{}
Counter::~Counter()
{}

Counter Counter::Add(const Counter &rhs)
{
	return Counter(itsVal + rhs.GetItsVal());
}

int main()
{
	Counter varOne(2), varTwo(4), varThree;
	varThree = varOne.Add(varTwo);
	cout << "varOne: " << varOne.GetItsVal() << endl;
	cout << "varTwo: " << varTwo.GetItsVal() << endl;
	cout << "varThree: " << varThree.GetItsVal() << endl;
	return 0;
}


Output: varOne: 2
varTwo: 4
varThree: 6

The Add() function is new in this listing and takes a const reference to a Counter object; it returns a Counter object. An additional constructor is created because varOne and varTwo need to be initialized to a nonzero value anmd the default one would not suffice.

Although we have defined a new function Add(), it does not overload the operator+, here is it overloaded:

#include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	Counter(int initialValue);
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	void Increment() {++itsVal;}
	Counter operator+ (const Counter &);

private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::Counter(int initialValue):
itsVal(initialValue)
{}
Counter::~Counter()
{}

Counter Counter::operator+(const Counter &rhs)
{
	return Counter(itsVal + rhs.GetItsVal());
}

int main()
{
	Counter varOne(2), varTwo(4), varThree;
	varThree = varOne + varTwo;
	cout << "varOne: " << varOne.GetItsVal() << endl;
	cout << "varTwo: " << varTwo.GetItsVal() << endl;
	cout << "varThree: " << varThree.GetItsVal() << endl;
	return 0;
}


Output: varOne: 2
varTwo: 4
varThree: 6

Note that the output is exaclty the same, excpet we have just overloaded the operator+. You can use this method to overload any arithmetic type, add, subtract, etc...

Overloading operators << and []

This next portion assumes you understand how ‘friend’ functions work. The syntax for these operators is essentially the same as above, but requires modification in regards to what classes/objects they are being placed into:

 type operator[](parameter list)
type operator<<(parameter list)


This code is a good example of how both of these are used in regards to strings. When you have printed a string in the past it was:

 cout << theString.GetString();


What we “want” to do is this:

 cout << theString;


See here:

 #include <iostream>
#include <string.h>
using namespace std;

class String
{
public:
	String();
	String(const char *const);
	String(const String &);
	~String();

	//overloaded operators
	char & operator[](int offset);
	char operator[](int offset) const;
	String operator+(const String&);
	void operator+=(const String&);
	String & operator= (const String&);
	friend ostream& operator<<
		(ostream& theStream, String& theString);
	int GetLen()const {return itsLen;}
	const char * GetString() const {return itsString;}

private:
	String (int);
	char * itsString;
	unsigned short itsLen;
};//end String class

String::String()
{
	itsString = new char[1];
	itsString[0] = '\0';
	itsLen = 0;
}

String::String(int len)
{
	itsString = new char [len+1];
	for(int i = 0; i <= len; i++)
		itsString[i] = '\0';
	itsLen = len;
}

String::String(const char * const cString)
{
	itsLen = strlen(cString);
	itsString = new char[itsLen+1];
	for (int i = 0; i <itsLen; i++)
		itsString[i] = cString[i];
	itsString[itsLen] = '\0';
}

String::String(const String & rhs)
{
	itsLen=rhs.GetLen();
	itsString = new char[itsLen+1];
	for (int i =0; i< itsLen; i++)
		itsString[i] = rhs[i];
	itsString[itsLen] = '\0';
}

String::~String()
{
	delete [] itsString;
	itsLen = 0;
}

String& String::operator=(const String & rhs)
{
	if (this == &rhs)
		return *this;
	delete [] itsString;
	itsLen = rhs.GetLen();
	itsString = new char [itsLen +1];
	for (int i = 0; i <itsLen; i++)
		itsString[i] = rhs[i];
	itsString[itsLen] = '\0';
	return *this;
}

char & String::operator [](int offset)
{
	if (offset > itsLen)
		return itsString[itsLen-1];
	else
		return itsString[offset];
}

char String::operator [](int offset) const
{
	if (offset > itsLen)
		return itsString[itsLen-1];
	else
		return itsString[offset];
}

String String::operator +(const String & rhs)
{
	int totalLen = itsLen + rhs.GetLen();
	String temp(totalLen);
	int i, j;
	for (i = 0; i < itsLen; i++)
		temp[i] = itsString[i];
	for(j = 0; j < rhs.GetLen(); j ++)
		temp[i] = rhs[j];
	temp[totalLen]='\0';
	return temp;
}

void String::operator +=(const String &rhs)
{
	unsigned short rhsLen = rhs.GetLen();
	unsigned short totalLen = itsLen + rhsLen;
	String temp(totalLen);
	int i, j;
	for (i=0; i<itsLen; i++)
		temp[i] = itsString[i];
	for(j=0, i=0; j<rhs.GetLen(); j++, i++)
		temp[i] = rhs[i-itsLen];
	temp[totalLen] = '\0';
	*this = temp;
}

ostream& operator<< (ostream& theStream, String& theString)
{
	theStream << theString.itsString;
	return theStream;
}

int main()
{
	String theString("Example of Overloading Operators\n");
	cout << theString;
	return 0;
}


By overloading the [] and << operators, we were able to display a string without an extra function call.


The Assignment Operator= ()

This operator comes in handy when you want to assign the value of one object to another. This can create confusion as there are shallow copies (just the members) and a deep copy (which allocates the necessary memory for that object). Example:

Cat catOne(5,7);
		 Cat catTwo(3,4);
		// some other code....
		catTwo = catOne;


This snippet has each object with a few of its assigned member values. What if you wanted to assign them to something else?

 catTwo = catTwo; 


I doubt anyone would do this on purpose, but it is possible for it happen by accident when references and dereferenced pointers hide the fact that this assignment is to itself. If this was not handled catTwo would delete its own memory allocation and then when it was ready to copy from the right side, there would be nothing there... For protection against this any assignment operator you create must check for itself using the 'this' pointer discussed earlier:

# include <iostream>
using namespace std;

class Cat
{
public:
	Cat();
	//copy constructor and destructor omitted
	int GetAge()const {return *itsAge;}
	int GetWeight()const {return *itsWeight;}
	void SetAge(int age){*itsAge = age;}
	Cat & operator=(const Cat&);

private:
	int *itsAge;
	int *itsWeight;
};

Cat::Cat()
{
	itsAge = new int;
	itsWeight = new int;
	*itsAge = 5;
	*itsWeight = 9;
}

Cat & Cat::operator=(const Cat & rhs)
{
	if (this == &rhs)
		return *this;
	*itsAge = rhs.GetAge();
	*itsWeight = rhs.GetWeight();
	return *this;
}

int main()
{
	Cat Frisky;
	cout << "Frisky's age: " << Frisky.GetAge() << endl;
	cout << "Setting Frisky's age to 6...\n";
	Frisky.SetAge(6);
	Cat Whiskers;
	cout << "Whisker's age: " << Whiskers.GetAge() << endl;
	cout << "copying Frisky to Whiskers....\n";
	Whiskers = Frisky;
	cout << "Whiskers's age: " << Whiskers.GetAge() << endl;
	return 0;
}



Output: Frisky's age: 5
Setting Frisky's age to 6...
Whisker's age: 5
copying Frisky to Whiskers
Whisker's age: 6

Data conversion was handled and would not cause an error is an object is assigned to itself. Going back to the Counter example, what happens if you wished to assign an integer value to a Counter object? There would be an error. This is how to handle such problems:

#include <iostream>
using namespace std;

class Counter
{
public:
	Counter();
	Counter(int val);
	~Counter();
	int GetItsVal()const {return itsVal;}
	void SetItsVal(int x) {itsVal = x;}
	operator unsigned int();
private:
	int itsVal;
};

Counter::Counter():
itsVal(0)
{}

Counter::Counter(int val):
itsVal(val)
{}
Counter::~Counter()
{}

Counter::operator unsigned int()
{
	return (int (itsVal));
}

int main()
{
	Counter ctr(5);
	int someInt = ctr;
	cout << "someInt: " << someInt << endl;
	return 0;
}


By creating a conversion operator, the program now can switch between ints and Counter objects, vice versa, you can even add other data types, etc...


Limitations on Operaton Overloading

Operators for built in types (such as int, double) cannot be overloaded. Also the precedence order cannot be changed, nor the arity of the operator, i.e. unary or binary. As fun as it sounds, you can't create your own "new" operators like '**' being a power operator.
You could also make the operator+ subtract and the operator * divide, which shows the powerful abilities this has, but also shows impracticality. Code that is easier to read, smooth, concise, and practical is better then confusing pointless code. :)

Hope this helped you in regards to overloading operators in C++. Any extra input/criticism would be greatly appreciated. :)

Is This A Good Question/Topic? 1
  • +

Replies To: Overloading Operators

#2 jjhaag  Icon User is offline

  • me editor am smartastic
  • member icon

Reputation: 44
  • View blog
  • Posts: 1,789
  • Joined: 18-September 07

Posted 27 September 2007 - 03:59 PM

having just given it a once-over, looks very nice. thanks for the resource.

one (very) small point:

"So how does one overload the prefix operator?"
should probably be
So how does one overload the prefix increment operator?

thanks again.

-jjh
Was This Post Helpful? 0
  • +
  • -

#3 KYA  Icon User is offline

  • g++ jameson.cpp -o beverage
  • member icon

Reputation: 3101
  • View blog
  • Posts: 19,141
  • Joined: 14-September 07

Posted 27 September 2007 - 06:46 PM

View Postjjhaag, on 27 Sep, 2007 - 03:59 PM, said:

having just given it a once-over, looks very nice. thanks for the resource.

one (very) small point:

"So how does one overload the prefix operator?"
should probably be
So how does one overload the prefix increment operator?

thanks again.

-jjh



Thanks for pointing that out :) I'll fix it :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1