- Back to Home »
- Operator Overloading
Posted by : Sushanth
Monday, 14 December 2015
Operator Overloading:
Operator overloading is the ability to tell the compiler
how to perform a certain operation when its corresponding operator is used on
one or more variables.
By overloading standard
operators on a class, you can exploit the intuition of the users of that class.
This lets users program in the language of the problem domain rather than in
the language of the machine. The ultimate goal is to reduce both the learning
curve and the defect rate.
C++ supports a set of operators for its built-in
types. To use these operators for user defined types, these operators should be
defined in the context of the class. For example, there is no built-in operator
to add two complex numbers. To add two complex numbers, the + operator should
be overloaded in a class as shown below.
+
Operator:
Below program overloads + operator to add two
complex numbers
#include "stdafx.h"
#include <iostream>
using namespace
std;
class Complx
{
double
real,imag;
public:
Complx(int
real,int imag):real(real),imag(imag)
{
}
Complx operator+(const Complx& C)
{
Complx temp(0,0);
temp.real = this->real
+ C.real;
temp.imag = this->imag
+ C.imag;
return
temp;
}
};
int _tmain(int
argc, _TCHAR* argv[])
{
Complx x(4,5);
Complx y(4,5);
Complx Z = x + y; // x.operator+(y)
y is a parameter and it should
return complx object
return 0;
}
Here the complx class has
two data members real and imag.In operator+() method,we have added real and
imaginary parts of the class instances seperately to add two complex numbers.
Below fig shows the way to
build the operator+ signature.
In main() function,the
call to add two complex numbers is expanded as shown below.
An overloaded operator is called an operator
function. You declare an operator function with the keyword operator
preceding the operator.
Note 1: The
following are the list of the operators which can be overloaded.
+
|
-
|
*
|
/
|
%
|
^
|
&
|
|
|
~
|
!
|
=
|
<
|
>
|
+=
|
-=
|
*=
|
/=
|
%=
|
^=
|
&=
|
|=
|
<<
|
>>
|
<<=
|
>>=
|
==
|
!=
|
<=
|
>=
|
&&
|
||
|
++
|
--
|
,
|
->*
|
->
|
( )
|
[ ]
|
new
|
delete
|
new[]
|
delete[]
|
|
|
|
where
() is the function call operator and [] is the subscript operator.
You
can overload both the unary and binary forms of the following operators:
-
|
*
|
&
|
Note 2: The following operators cannot be overloaded
. (member
selection)
|
.* (member selection through pointer to function)
|
:: (scope resolution)
|
?:
|
The
above parameters take a name rather than a value as their second operand and
provide the primary means of referring to members. Allowing them to overload
will lead to subtleties.
You
cannot overload the preprocessor symbols # and ##.
Note3:
An
overloaded operator (except for the function call operator) cannot have default
arguments or an ellipsis in the argument list.
void operator-(int i = 0) //error C2831:
'operator -' cannot have
default parameters
{
//error C2333: 'Complx::operator -' : error
in function declaration; skipping function
body
}
Binary unary operator can be overloaded with either a nonstatic member function that has one parameter, or a nonmember function that has two parameters.
A
nonstatic member function that overloads this operator would have the following
form:
return_type
operator@(T)
A
nonmember function that overloads the same operator would have the following
form:
return_type operator@(T,
U)
*Operator:
#include "stdafx.h"
#include <iostream>
using namespace
std;
class Complx
{
double
real,imag;
public:
Complx(int
real,int imag):real(real),imag(imag)
{
}
Complx operator*(const Complx &C)
{
Complx temp(0,0);
temp.real = (this->real
* C.real);
temp.imag = (this->imag
* C.imag);
return
temp;
}
};
int _tmain(int
argc, _TCHAR* argv[])
{
Complx x(4,5);
Complx y(4,5);
Complx Zmultiply
= x * y;
return 0;
}
Overloading Function call
operator:
#include "stdafx.h"
#include <iostream>
using namespace
std;
class FuncCall
{
public:
FuncCall(){}
void operator()(int a,int b,char z,char c){}
void operator()(int a,int b){}
};
int _tmain(int
argc, _TCHAR* argv[])
{
FuncCall f1,f2;
f1(4,5,'Z','a'); // error C2064:
term does not evaluate to a function taking 4 arguments
//f1.operator()(..params..)
//To
support this overload function call operator ()
f1(4,5);
return 0;
}
The function call operator, when
overloaded, does not modify how functions are called. Rather, it modifies how
the operator is to be interpreted when applied to objects of a given type.
You overload the function call
operator, operator (), with a nonstatic member function that has any number of
parameters. If you overload a function call operator for a class its
declaration will have the following form:
return_type operator()(parameter_list)
Unlike all other overloaded operators, you can
provide default arguments and ellipses in the argument list for the function
call operator.
class Point
{
private:
int x, y;
public:
Point() : x(0), y(0) { }
Point& operator()(int dx, int dy) {
x += dx;
y += dy;
return *this;
}
};
int main() {
Point pt;
// Offset this
coordinate x with 3 points
// and coordinate y
with 2 points.
pt(3, 2);
}
Overloading Assignment operator:
#include "stdafx.h"
#include <iostream>
using namespace
std;
class Assign
{
int a;
public:
Assign()
{
cout<<"constructor"<<endl;
}
Assign &operator=(Assign
&a)
{
return
a;
}
Assign &operator=(int a)
{
this->a
= a;
return
*this;
}
};
int _tmain(int
argc, _TCHAR* argv[])
{
Assign a1,a2,a3;
//TO support
below expression,we need to overload assignment operator
a1 = 5; //
a1.operator=(5)
a2 = a1; //a2.operator=(a1)
return 0;
}
The assignment x1 = x2 calls the copy assignment
operator X& X::operator=(X&). The assignment x1 = 5 calls the copy
assignment operator X& X::operator=(int). The compiler implicitly declares
a copy assignment operator for a class if you do not define one yourself.
Consequently, the copy assignment operator (operator=) of a derived class hides
the copy assignment operator of its base class..If we
have some pointer data members then we should overload = operator to avoid
shallow copy.
Note:
assignment operator can be overloaded with a nonstatic member function that has
only one parameter. You cannot declare an overloaded assignment operator that
is a nonmember function.
Overloading subscription
operator:
You
overload operator[]
with a nonstatic member function that has only one parameter. The following
example is a simple array class that has an overloaded subscripting operator.
#include "stdafx.h"
#include <string>
#include <iostream>
using namespace
std;
template <class
T>
class MyArray
{
T *arr;
T size;
public:
MyArray(T size)
{
arr = new
T[size];
this->size
= size;
}
~MyArray()
{
delete[]
arr;
arr = NULL;
}
T &operator[](T
location)
{
if(location<0)
cout<<"index out of range"<<endl;
else
return
arr[location];
}
};
int _tmain(int
argc, _TCHAR* argv[])
{
MyArray<int>
Col (3);
cout<< Col [0] <<endl;
return
0;
}
The expression x[1] is interpreted as x.operator[](1) and calls int& MyArray<int>::operator[](const int).
Overloading Pre and
post fix operators:
The prefix increment operator ++ can
be overloaded with either a nonmember function operator that has one argument
of class type or a reference to class type, or with a member function operator
that has no arguments.
#include "stdafx.h"
class A
{
int a;
public:
A(){}
//pre-increment
operator
int operator++()
{
a = a + 1;
return
this->a;
}
void operator--()
{
a = a - 1;
}
//post increment
operator - add a parameter to
// method to make
it post increment operator
void operator++(int)
{
a= a+1;
}
void operator--(int a)
{
}
};
int _tmain(int
argc, _TCHAR* argv[])
{
A a;
int value =
++a;
--a;
a++;
return 0;
}
The
postfix increment operator ++ can be overloaded for a class type by
declaring a nonmember function operator operator++() with two
arguments, the first having class type and the second having type int.
Alternatively, you can declare a member function operator operator++()
with one argument having type int. The compiler uses the int
argument to distinguish between the prefix and postfix increment operators.
#include <iostream>
using namespace std;
class Digit
{
private:
int m_digit;
public:
Digit(int ndigit=0){
m_digit=ndigit;
}
Digit& operator++();//prefix
Digit& operator--(); //prefix
Digit operator++(int);
Digit operator--(int);
int get() const { return m_digit;}
};
Digit& Digit::operator++(){
++m_digit;
return *this;
}
Digit& Digit::operator--(){
--m_digit;
return *this;
}
Digit Digit::operator++(int){
Digit cresult(m_digit);
++(*this);
return cresult;
}
Digit Digit::operator--(int){
Digit cresult(m_digit);
--(*this);
return cresult;
}
int main(){
Digit cDigit(5);
++cDigit;
cDigit++;
cout<<cDigit.get()<<endl;
cout<<cDigit.get()<<endl;
return 0;
}
The pre- and post-increment are two distinct operators, and require
separate overloads.
C++ doesn't allow overloading solely on return type, so having
different return types as in your example wouldn't be sufficient to
disambiguate the two methods.
The dummy argument is the mechanism that the designer of C++ chose
for the disambiguation.
Guidelines:
- Use common sense. If your overloaded operator
makes life easier and safer for your users, do it; otherwise don't. This
is the most important guideline. In fact it is, in a very real sense, the
only guideline; the rest are just special cases.
- If you define arithmetic operators, maintain
the usual arithmetic identities. For example, if your class defines x + y and x - y, then x + y - y ought to return an object that is
behaviorally equivalent to x. The term behaviorally equivalent is defined
in the bullet on x
== y below, but
simply put, it means the two objects should ideally act like they have the
same state. This should be true even if you decide not to define an == operator for objects of your class.
- You should provide arithmetic operators only
when they make logical sense to users. Subtracting two dates makes sense,
logically returning the duration between those dates, so you might want to
allow date1 - date2 for objects of your Date class (provided you have a reasonable class/type to represent the
duration between two Date objects). However adding two dates makes no
sense: what does it mean to add July 4, 1776 to June 5, 1959? Similarly it
makes no sense to multiply or divide dates, so you should not define any
of those operators.
- You should provide mixed-mode arithmetic
operators only when they make logical sense to users. For example, it
makes sense to add a duration (say 35 days) to a date (say July 4, 1776),
so you might define date
+ duration to
return a Date. Similarly date - duration could also return a Date. But duration - date does not make sense at the conceptual level
(what does it mean to subtract July 4, 1776 from 35 days?) so you should
not define that operator.
- If you provide constructive operators, they
should return their result by value. For example, x + y should return its result by value. If it returns by reference, you
will probably run into lots of problems figuring out who owns the referent
and when the referent will get destructed. Doesn't matter if returning by
reference is more efficient; it is probably wrong. See the next
bullet for more on this point.
- If you provide constructive operators, they
should not change their operands. For example, x + y should not change x. For some crazy reason, programmers often
define x + y to be logically the same as x += y because the latter is faster. But remember,
your users expect x +
y to make a
copy. In fact they selected the + operator
(over, say, the += operator) precisely because they wanted
a copy. If they wanted to modify x, they would
have used whatever is equivalent to x += y instead. Don't make semantic decisions for
your users; it's their decision, not yours, whether they want the
semantics of x + y vs. x += y. Tell them that one is faster if you want,
but then step back and let them make the final decision — they know what
they're trying to achieve and you do not.
- If you provide constructive operators, they
should allow promotion of the left-hand operand (at least in the case
where the class has a single-parameter ctor that is not marked with the explicit keyword). For example, if your class Fraction supports promotion from int to Fraction (via the non-explicit ctor Fraction::Fraction(int)), and if you allow x - y for two Fraction objects, you should also allow 42 - y. In practice that simply means that your operator-() should not be a member function of Fraction. Typically you will make it a friend, if for no other reason than to force it into the public: part of the class, but even if it is not a friend, it should
not be a member.
- In general, your operator should change its
operand(s) if and only if the operands get changed when you apply the same
operator to intrinsic types. x == y and x << y should not change either operand; x *= y and x <<= y should (but only the left-hand operand).
- If you define x++ and ++x, maintain the usual identities. For example, x++ and ++x should have the same observable effect on x, and should differ only in what they return. ++x should return x by reference; x++ should either return a copy (by value) of the original state of x or should have a void return-type. You're usually better off
returning a copy of the original state of x by value, especially if your class will be used in generic
algorithms. The easy way to do that is to implement x++ using three lines: make a local copy of *this, call ++x (i.e., this->operator++()), then return the local copy. Similar
comments for x-- and --x.
- If you define ++x and x += 1, maintain the usual identities. For example,
these expressions should have the same observable behavior, including the
same result. Among other things, that means your += operator should return x by
reference. Similar comments for --x and x -= 1.
- If you define *p and p[0] for pointer-like objects, maintain the usual
identities. For example, these two expressions should have the same result
and neither should change p.
- If you define p[i] and *(p+i) for pointer-like objects, maintain the usual
identities. For example, these two expressions should have the same result
and neither should change p. Similar comments for p[-i] and *(p-i).
- Subscript operators generally come in pairs;
see on const-overloading.
- If you define x == y, then x == y should be true if and only if the two objects
are behaviourally equivalent. In this bullet, the term "behaviorally
equivalent" means the observable behavior of any operation or
sequence of operations applied to x will be the same as when applied to y. The term "operation" means methods, friends, operators,
or just about anything else you can do with these objects (except, of
course, the address-of operator). You won't always be able to achieve that
goal, but you ought to get close, and you ought to document any variances
(other than the address-of operator).
- If you define x == y and x = y, maintain
the usual identities. For example, after an assignment, the two objects
should be equal. Even if you don't define x == y, the two objects should be behaviorally
equivalent (see above for the meaning of that phrase) after an assignment.
- If you define x == y and x != y, you
should maintain the usual identities. For example, these expressions
should return something convertible to bool, neither should change its operands, and x == y should have the same result as !(x != y), and vice versa.
- If you define inequality operators like x <= y and x < y, you
should maintain the usual identities. For example, if x < y and y < z are
both true, then x
< z should also
be true, etc. Similar comments for x >= y and x > y.
- If you define inequality operators like x < y and x >= y, you should maintain the usual identities. For example, x < y should have the result as !(x >= y). You can't always do that, but you should get
close and you should document any variances. Similar comments for x > y and !(x <= y), etc.
- Avoid overloading short-circuiting operators: x || y or x && y. The overloaded versions of these do not short-circuit — they
evaluate both operands even if the left-hand operand
"determines" the outcome, so that confuses users.
- Avoid overloading the comma operator: x, y. The overloaded comma operator does not have the same ordering
properties that it has when it is not overloaded, and that confuses users.
- Don't overload an operator that is
non-intuitive to your users. This is called the Doctrine of Least
Surprise. For example, altough C++ uses std::cout << x for printing, and although printing is
techincally called inserting, and although inserting sort of sounds like
what happens when you push an element onto a stack, don't overload myStack << x to push an element onto a stack. It might
make sense when you're really tired or otherwise mentally impaired, and a
few of your friends might think it's "kewl," but just say No.
- Use common sense. If you don't see
"your" operator listed here, you can figure it out. Just remember
the ultimate goals of operator overloading: to make life easier for your
users, in particular to make their code cheaper to write and more obvious.
Problem:
Find no of objects created on the heap and no
of objects created on the stack
#include "stdafx.h"
#include <new>
#include <cstdlib>
#include <iostream>
using namespace
std;
class A
{
private:
static int nStack; //count for
objects created on stack
static int nHeap; //count for objects created on heap
public:
A()
{
nStack++;
}
static int getStackObjNo() //returns
stack objs count
{
return
nStack;
}
void* operator
new(size_t size) //overload
new such that it calls
malloc for memory
{ //allocation and calls constructor
void*
p = malloc(size);
nHeap++;
return
p;
}
static int getHeapObjNo(){return
nHeap;} //returns heap objects count
void operator delete(void *p){free(p);} //overload
delete operator
};
int A::nStack = 0;
int A::nHeap = 0;
int _tmain(int
argc, _TCHAR* argv[])
{
A *a = new
A();
A a1;
A *b = new
A();
cout<<A::getStackObjNo()<<endl;
cout<<A::getHeapObjNo()<<endl;
delete a;
delete b;
return 0;
}
Which is more
efficient: i++ or ++i?
++i is sometimes faster than, and is never slower
than, i++.
For intrinsic types like int, it doesn't matter: ++i
and i++ are the same speed. For class types like iterators or the previous
FAQ's Number class, ++i very well might be faster than i++ since the latter
might make a copy of this object.