Startertutorials Blog
Tutorials and articles related to programming, computer science, technology and others.
Subscribe to Startertutorials.com's YouTube channel for different tutorial and lecture videos.

Categories: C++ Programming. No Comments on Operator overloading in C++

This article provides a comprehensive overview of operator overloading in C++ programming language along with example programs.

 

Introduction

 

The feature of C++ which allows programmers to redefine the meaning of operators to make them work with classes and objects is operator overloading.

 

While evaluating expressions, C++ compiler checks whether the operands are of normal types or user-defined types. If the operands are of built-in or normal type, it performs the regular operation. If the operands are of user-defined type, compiler checks if there is a function available with an operator or not. If no such function is available, it throws an error.

 

Operator overloading is another type of compile-time polymorphism like function overloading and constructor overloading. Operator overloading is provided by the language, programmer, or both.

 

Advantages of Operator Overloading

 

Following are the advantages of operator overloading:

  • Operator overloading enables programmers to use notation closer to the target domain. For example we can add two matrices by writing M1 + M2 rather than writing M1.add(M2).
  • Operator overloading provides similar syntactic support of built-in types to user-defined types.
  • Operator overloading makes the program easier to understand.

 

Rules for Operator Overloading

 

Following are the rules for overloading operators in C++:

  1. Only built-in operators can be overloaded.
  2. Degree or arity of the operators cannot be changed.
  3. Precedence and associativity of the operator cannot be changed.
  4. Overloaded operator cannot have default arguments, except for () operator.
  5. At least one operand must be of user-defined type.
  6. =, [], (), -> must be defined as member functions. Remaining operators can be either member or non-member functions.
  7. Some operators like =, &, and comma operator are already overloaded by default.
  8. Operators like ::, dot, and ?: cannot be overloaded.

 

Syntax for Operator Overloading

 

An operator that is to be overloaded is declared in the public section. The syntax for an operator overloading function is as follows:

 

class ClassName
{
	...
	public:
		...
		return-type operator op(params-list)
		{
			//body of the function
			...
		}
		...
};

 

In the above syntax, op is the operator to be overloaded and operator is a keyword. For example, the function for overloading + operator for adding two complex numbers will be as follows:

 

Complex operator +(Complex &c2)
{
	Complex temp;
	temp.real = real + c2.real;
	temp.imag = imag + c2.imag;
	return temp;
}

 

Now, if c1 and c2 are two objects of Complex class, we can add them by writing c3 = c1+ c2.

 

Complete program for adding two complex numbers is as follows:

 

#include <iostream>
using namespace std;
class Complex
{
	private:
		float real;
		float imag;
	public:
		Complex() {}
		Complex(float a, float b)
		{
			real = a;
			imag = b;
		}
		Complex operator +(Complex &c2)
		{
			Complex temp;
			temp.real = real + c2.real;
			temp.imag = imag + c2.imag;
			return temp;
		}
		void show_details()
		{
			cout<<real<<"+i"<<imag;
		}
};
int main()
{
	Complex c1(5, 4);
	Complex c2(3, 1);
	Complex c3 = c1 + c2;
	c3.show_details();
	return 0;
}

Output of the above program is as follows:

8+i5

 

Operators that cannot be Overloaded

 

Although most of the operators in C++ can be overloaded, there are some operators which cannot be overloaded. They are: scope resolution operator (::), member selection operator (.) and ternary operator (?:). Following are some points to remember about operator overloading:

  • Operator overloading extends the semantics without changing its syntax.
  • Some operators like assignment operator (=) and address operator (&) are already overloaded by C++.
  • Operator overloading does not alter the precedence and associativity of operators.
  • New operators cannot be created using operator overloading.
  • When operators such as &&, || are overloaded, they lose their special properties of short-circuit evaluation and sequencing.
  • Overloaded operators cannot have default arguments.
  • All overloaded operators except the assignment operator are inherited by the derived class.
  • Number of operands cannot be changed for an operator using operator overloading.

 

Implementing Operator Overloading

 

Operator overloading can be implemented in two ways. They are:

  1. Using a member function
  2. Using a friend function

 

Differences between using a member function and a friend function to implement operator overloading are as follows:

 

differences-bw-member-friend-function

 

Overloading Unary Operators

 

Operators which work on a single operand are known as unary operators. Examples are: increment operator(++), decrement operator(–), unary minus operator(-), logical not operator(!) etc.

 

Using a member function to overload an unary operator

 

Following program demonstrates overloading unary minus operator using a member function:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		void operator -()
		{
			x = -x;
		}
		void display()
		{
			cout<<"x = "<<x;
		}
};
int main()
{
	Number n1(10);
	-n1;
	n1.display();
	return 0;
}

Output of the above program is as follows:

x = -10

 

In the above program the value of x is changed and printed using the function display(). We can return an object of class Number after modifying x as shown in the following program:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		Number operator -()
		{
			x = -x;
			return Number(x);
		}
		void display()
		{
			cout<<"x = "<<x<<endl;
		}
};
int main()
{
	Number n1(10);
	Number n2 = -n1;
	n2.display();
	return 0;
}

Output of the above program is as follows:

x = -10

 

Using a friend function to overload an unary operator

 

When a friend function is used to overload an unary operator following points must be taken care of:

  • The function will take one operand as a parameter.
  • The operand will be an object of a class.
  • The function can access the private members only though the object.
  • The function may or may not return any value.
  • The friend function will not have access to the this

 

Following program demonstrates overloading an unary operator using a friend function:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		friend Number operator -(Number &);		
		void display()
		{
			cout<<"x = "<<x<<endl;
		}
};
Number operator -(Number &n)
{
	return Number(-n.x);
}
int main()
{
	Number n1(20);
	Number n2 = -n1;
	n2.display();
	return 0;
}

Output of the above program is as follows:

x = -20;

 

Overloading prefix operators

 

The syntax for overloading prefix increment and decrement operators is as follows:

 

return-type operator ++( )
{
	//Body of the function
	...
}

 

Following program demonstrates overloading prefix increment and decrement operators:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		Number operator ++()
		{
			x = x + 1;
			return Number(x);
		}
		Number operator --()
		{
			x = x - 1;
			return Number(x);
		}
		void display()
		{
			cout<<"x = "<<x<<endl;
		}
};
int main()
{
	Number n1(20);
	Number n2 = ++n1;
	n2.display();
	Number n3(20);
	Number n4 = --n3;
	n4.display();
	return 0;
}

Output of the above program is as follows:

x = 21
x = 19

 

Overloading postfix operators

 

When overloading postfix increment and decrement operators we have to include an extra integer parameter to avoid confusion with prefix operators. Syntax for overloading postfix increment operator is as follows:

 

return-type operator ++(int)
{
	//Body of function
	...
}

 

The int parameter is a dummy parameter and there is no need to give any name for it. Following program demonstrates overloading postfix increment and decrement operators:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		Number operator ++(int)
		{
			return Number(x++);
		}
		Number operator --(int)
		{
			return Number(x--);
		}
		void display()
		{
			cout<<"x = "<<x<<endl;
		}
};
int main()
{
	Number n1(20);
	Number n2 = n1++;
	n1.display();
	n2.display();
	Number n3 = n2--;
	n3.display();
	n2.display();
	return 0;
}

Output of the above program is as follows:

x = 21
x = 20
x = 20
x = 19

 

Overloading Binary Operators

 

As unary operators can be overloaded, we can also overload binary operators. Syntax for overloading a binary operator using a member function is as follows:

 

return-type operator op(ClassName &)
{
	//Body of function
	...
}

 

Syntax for overloading a binary operator using a friend function is as follows:

 

return-type operator op(ClassName &, ClassName &)
{
	//Body of function
	...
}

 

Following program demonstrates overloading the binary operator + using a member function:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number() {}
		Number(int x)
		{
			this->x = x;
		}
		Number operator +(Number &n)
		{
			Number temp;
			temp.x = x + n.x;
			return temp;
		}
		void display()
		{
			cout<<"x = "<<x<<endl;
		}
};
int main()
{
	Number n1(20);
	Number n2(10);
	Number n3 = n1 + n2;
	n3.display();
	return 0;
}

Output of the above program is as follows:

x = 30

 

Following program demonstrates overloading the binary operator + using a friend function:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number() {}
		Number(int x)
		{
			this->x = x;
		}
		friend Number operator +(Number &, Number &);
		void display()
		{
			cout<<"x = "<<x<<endl;
		}
};
Number operator +(Number &n1, Number &n2)
{
	Number temp;
	temp.x = n1.x + n2.x;
	return temp;
}
int main()
{
	Number n1(20);
	Number n2(10);
	Number n3 = n1 + n2;
	n3.display();
	return 0;
}

Output of the above program is as follows:

x = 30

 

Note: Operators such as =, ( ), [ ], and -> cannot be overloaded using friend functions.

 

Overloading Special Operators

 

Some of the special operators in C++ are:

  • new – used to allocate memory dynamically
  • delete – used to free memory dynamically
  • ( ) and [ ] – subscript operators
  • -> – member access operator

 

Let’s see how to overload them.

 

Overloading new and delete operators

 

C++ allows programmers to overload new and delete operators due to following reasons:

  • To add more functionality when allocating or deallocating memory.
  • To allow users to debug the program and keep track of memory allocation and deallocation in their programs.

 

Syntax for overloading new operator is as follows:

 

void* operator new(size_t size);

 

Parameter size specifies the size of memory to be allocated whose data type is size_t. It returns a pointer to the memory allocated.

 

Syntax for overloading delete operator is as follows:

 

void operator delete(void*);

 

The function receives a parameter of type void* and returns nothing. Both functions for new and delete are static by default and can’t access this pointer. To delete an array objects, the operator delete[ ] must be overloaded.

 

Following program demonstrates overloading both new and delete operators:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		void* operator new(size_t size)
		{
			void *ptr = ::new int[size];	//Using global new operator
			cout<<"Memory allocated of size: "<<size<<endl;
			return ptr;
		}
		void operator delete(void *ptr)
		{
			::delete(ptr);	//Using global delete operator
			cout<<"Memory deallocated"<<endl;
		}
		void display()
		{
			cout<<"x = "<<x<<endl;
		}
};
int main()
{
	Number *n = new Number(10);   //Invokes overloaded new operator
	n->display();
	delete n;	//Invokes overloaded delete operator
	return 0;
}

Output of the above program is as follows:

Memory allocated of size: 4
x = 10
Memory deallocated

 

In the above program, ::new and ::delete refers to the global new and delete operators. When new is called, compiler executes the overloaded function for new and also automatically calls the constructor.

 

Advantages of overloading new and delete operators

  • The overloaded new operator function can receive one or more parameters. This allows flexibility in customizing memory allocation.
  • Overloaded delete operator provides garbage collection for objects of classes.
  • Programmers can add exception handling code while allocating memory.
  • Programmers can use memory management functions like malloc(), realloc(), and free() inside the overloaded functions of new and delete

 

Overloading subscript operators [ ] and ( )

 

The subscript operator [ ] is used to access array elements. The function declared for overloading [ ] or ( ) should be non-static member function of a class. Syntax for overloading [ ] operator is as follows:

 

int& operator [ ](int x)
{
	//Body of function
	...
}

 

The overloaded function must return an integer value by reference.

 

Following example demonstrates overloading [ ] operator:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x[5];
	public:
		void read(int n)
		{
			cout<<"Enter "<<n<<" numbers: ";
			for(int i=0; i<n; i++)
			{
				cin>>x[i];
			}
		}
		int& operator [](int i)
		{
			return x[i];
		}
};
int main()
{
	Number n1;
	n1.read(5);
	cout<<"Element is: "<<n1[2];
	return 0;
}

Input and Output of the above program is as follows:

Enter 5 numbers: 1 2 3 4 5
Element is: 3

 

In case of multiple subscripts, we can overload ( ) operator instead of [ ] operator. Syntax for overloading ( ) operator is as follows:

 

int& operator ( ) (int i, int j,...)
{
	//Body of function
	...
}

 

Example for overloading ( ) operator is as follows:

 

#include <iostream>
using namespace std;
class Matrix
{
	private:
		int x[2][2];
	public:
		void read()
		{
			cout<<"Enter 2x2 matrix elements: ";
			for(int i=0; i<2; i++)
			{
				for(int j=0; j<2; j++)
					cin>>x[i][j];
			}
		}
		int& operator ()(int i, int j)
		{
			return x[i][j];
		}
};
int main()
{
	Matrix m;
	m.read();
	cout<<"Element is: "<<m(1,1);
	return 0;
}

Input and output for the above program is as follows:

Enter 2x2 matrix elements:
1 2
3 4
Element is: 4

 

Overloading class member access operator

 

Class member access can be controlled by overloading the class member access operator (->). It is an unary operator as it operates on only operand, the object. The overloaded function must be a non-static function and its syntax is as follows:

 

ClassName * operator ->(void)
{
	//Body of function
	...
}

 

Following program demonstrates overloading the member access operator (->):

 

#include <iostream>
using namespace std;
class Number
{
	public:
		int x;
		Number(int x)
		{
			this->x = x;
		}
		Number * operator ->()
		{
			return this;
		}
};
int main()
{
	Number n1(30);
	cout<<"x = "<<n1->x;
	return 0;
}

Output of the above program is as follows:

x = 30

 

Type Conversion

 

In expressions when there are constants or variables of different types (built-in), one or more types are converted to a destination type. Similarly, user-defined types like classes when used in expressions can also be converted to built-in types or vice versa. Following are the different possibilities of type conversion:

  • Conversion from basic type to class type
  • Conversion from class type to basic type
  • Conversion from one class type to another class type

 

Conversion from basic type to class type

 

A value or variable of a basic type or built-in type can be converted to a class member type using a constructor. Following program demonstrates conversion of a basic type to a class type:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		void display()
		{
			cout<<"x = "<<x;
		}
};
int main()
{
	Number n1 = 30;     //Conversion from int to Number
	n1.display();
	return 0;
}

Output of the above program is as follows:

x = 30

 

Conversion from class type to basic type

 

A class type can be converted into a basic type by overloading the casting operator. Syntax for overloading the casting operator is as follows:

 

operator typename( )
{
	//Body of function
	...
}

 

Remember following points while overloading the casting operator:

  • Function should be defined within the class.
  • Function does not have any return type.
  • Function does not take any arguments.
  • Casting operator is a unary operator which works on only one operand, the object.

 

Following program demonstrates converting a class type to basic type:

 

#include <iostream>
using namespace std;
class Number
{
	private:
		int x;
	public:
		Number(int x)
		{
			this->x = x;
		}
		operator int()
		{
			int temp = x;
			return x;
		}
};
int main()
{
	Number n1 = 20;
	cout<<"x = "<<n1;
	return 0;
}

Output of the above program is as follows:

x = 20

 

Conversion from one class type to another class type

 

While writing programs it is common to assign an object of one class type to an object of another class type as follows:

 

destination-object = source-object;

 

In the above notation destination-object is an object of destination class type and source-object is an object of source class type. The conversion from source class type to destination can be done in two ways based on in which class the conversion is performed.

 

Conversion in source class

 

When performing conversion in source class, the type cast operator should be overloaded. Syntax for overloading cast operator is as follows:

 

operator typename( )
{
	//body of function
	...
}

 

In the above syntax typename is generally the destination class name. Following program converts a square to a rectangle using conversion in source class:

 

#include <iostream>
using namespace std;
class Rectangle
{
	private:
		int length;
		int breadth;
	public:
		Rectangle(int l, int b)
		{
			length = l;
			breadth = b;
		}
		void display()
		{
			cout<<"length = "<<length<<", breadth = "<<breadth;
		}
};
class Square
{
	private:
		int side;
	public:
		Square(int s)
		{
			side = s;
		}
		void display()
		{
			cout<<"side = "<<side;
		}
		operator Rectangle()
		{
			Rectangle r = Rectangle(side, side);
			return r;
		}
};
int main()
{
	Rectangle rect(20, 30);
	Square sq(30);
	rect = sq;	//conversion from square to rectangle
	rect.display();
	return 0;
}

Output of the above program is as follows:

length = 30, breadth = 30

 

Conversion in destination class

 

When performing conversion in destination class, a constructor of destination class with one argument of type source class should be created. Syntax of the constructor is as follows:

 

DestinationClass(SourceClass object)
{
	//code of constructor
	...
}

 

Following program converts a square to a rectangle using conversion in destination class:

#include <iostream>
using namespace std;
class Square
{
	private:
		int side;
	public:
		Square(int s)
		{
			side = s;
		}
		int get_side()
		{
			return side;
		}
};
class Rectangle
{
	private:
		int length;
		int breadth;
	public:
		Rectangle(int l, int b)
		{
			length = l;
			breadth = b;
		}
		Rectangle(Square s)
		{
			length = breadth = s.get_side();
		}
		void display()
		{
			cout<<"length = "<<length<<", breadth = "<<breadth;
		}
};
int main()
{
	Rectangle rect(20, 30);
	Square sq(30);
	rect = sq;
	rect.display();
	return 0;
}

Output of the above program is as follows:

length = 30, breadth = 30

 

How useful was this post?

Click on a star to rate it!

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Suryateja Pericherla

Suryateja Pericherla, at present is a Research Scholar (full-time Ph.D.) in the Dept. of Computer Science & Systems Engineering at Andhra University, Visakhapatnam. Previously worked as an Associate Professor in the Dept. of CSE at Vishnu Institute of Technology, India.

He has 11+ years of teaching experience and is an individual researcher whose research interests are Cloud Computing, Internet of Things, Computer Security, Network Security and Blockchain.

He is a member of professional societies like IEEE, ACM, CSI and ISCA. He published several research papers which are indexed by SCIE, WoS, Scopus, Springer and others.

Leave a Reply

Your email address will not be published. Required fields are marked *