Unit 3
Operator Overloading
If we create two or more members having the same name but different in number or type of parameter, it is known as C++ overloading. In C++, we can overload:
- Methods,
- Constructors, and
- Indexed properties
It is because these members have parameters only.
Types of overloading in C++ are:
- Function overloading
- Operator overloading
C++ Function Overloading
Function Overloading is defined as the process of having two or more function with the same name, but different in parameters is known as function overloading in C++. In function overloading, the function is redefined by using either different types of arguments or a different number of arguments. It is only through these differences compiler can differentiate between the functions.
The advantage of Function overloading is that it increases the readability of the program because you don't need to use different names for the same action.
C++ Function Overloading Example
Let's see the simple example of function overloading where we are changing number of arguments of add() method.
// program of function overloading when number of arguments vary.
- #include <iostream>
- Using namespace std;
- Class Cal {
- Public:
- Static int add(int a,int b){
- Return a + b;
- }
- Static int add(int a, int b, int c)
- {
- Return a + b + c;
- }
- };
- Int main(void) {
- Cal C; // class object declaration.
- Cout<<C.add(10, 20)<<endl;
- Cout<<C.add(12, 20, 23);
- Return 0;
- }
Output:
30
55
Let's see the simple example when the type of the arguments vary.
// Program of function overloading with different types of arguments.
- #include<iostream>
- Using namespace std;
- Int mul(int,int);
- Float mul(float,int);
- Int mul(int a,int b)
- {
- Return a*b;
- }
- Float mul(double x, int y)
- {
- Return x*y;
- }
- Int main()
- {
- Int r1 = mul(6,7);
- Float r2 = mul(0.2,3);
- Std::cout << "r1 is : " <<r1<< std::endl;
- Std::cout <<"r2 is : " <<r2<< std::endl;
- Return 0;
- }
Output:
r1 is : 42
r2 is : 0.6
Function Overloading and Ambiguity
When the compiler is unable to decide which function is to be invoked among the overloaded function, this situation is known as function overloading.
When the compiler shows the ambiguity error, the compiler does not run the program.
Causes of Function Overloading:
- Type Conversion.
- Function with default arguments.
- Function with pass by reference.
- Type Conversion:
Let's see a simple example.
- #include<iostream>
- Using namespace std;
- Void fun(int);
- Void fun(float);
- Void fun(int i)
- {
- Std::cout << "Value of i is : " <<i<< std::endl;
- }
- Void fun(float j)
- {
- Std::cout << "Value of j is : " <<j<< std::endl;
- }
- Int main()
- {
- Fun(12);
- Fun(1.2);
- Return 0;
- }
The above example shows an error "call of overloaded 'fun(double)' is ambiguous". The fun(10) will call the first function. The fun(1.2) calls the second function according to our prediction. But, this does not refer to any function as in C++, all the floating point constants are treated as double not as a float. If we replace float to double, the program works. Therefore, this is a type conversion from float to double.
- Function with Default Arguments
Let's see a simple example.
- #include<iostream>
- Using namespace std;
- Void fun(int);
- Void fun(int,int);
- Void fun(int i)
- {
- Std::cout << "Value of i is : " <<i<< std::endl;
- }
- Void fun(int a,int b=9)
- {
- Std::cout << "Value of a is : " <<a<< std::endl;
- Std::cout << "Value of b is : " <<b<< std::endl;
- }
- Int main()
- {
- Fun(12);
- Return 0;
- }
The above example shows an error "call of overloaded 'fun(int)' is ambiguous". The fun(int a, int b=9) can be called in two ways: first is by calling the function with one argument, i.e., fun(12) and another way is calling the function with two arguments, i.e., fun(4,5). The fun(int i) function is invoked with one argument. Therefore, the compiler could not be able to select among fun(int i) and fun(int a,int b=9).
- Function with pass by reference
Let's see a simple example.
- #include <iostream>
- Using namespace std;
- Void fun(int);
- Void fun(int &);
- Int main()
- {
- Int a=10;
- Fun(a); // error, which f()?
- Return 0;
- }
- Void fun(int x)
- {
- Std::cout << "Value of x is : " <<x<< std::endl;
- }
- Void fun(int &b)
- {
- Std::cout << "Value of b is : " <<b<< std::endl;
- }
The above example shows an error "call of overloaded 'fun(int&)' is ambiguous". The first function takes one integer argument and the second function takes a reference parameter as an argument. In this case, the compiler does not know which function is needed by the user as there is no syntactical difference between the fun(int) and fun(int &).
C++ Operators Overloading
Operator overloading is a compile-time polymorphism in which the operator is overloaded to provide the special meaning to the user-defined data type. Operator overloading is used to overload or redefines most of the operators available in C++. It is used to perform the operation on the user-defined data type. For example, C++ provides the ability to add the variables of the user-defined data type that is applied to the built-in data types.
The advantage of Operators overloading is to perform different operations on the same operand.
Operator that cannot be overloaded are as follows:
- Scope operator (::)
- Sizeof
- Member selector(.)
- Member pointer selector(*)
- Ternary operator(?:)
Syntax of Operator Overloading
- Return_type class_name : : operator op(argument_list)
- {
- // body of the function.
- }
Where the return type is the type of value returned by the function.
Class_name is the name of the class.
Operator op is an operator function where op is the operator being overloaded, and the operator is the keyword.
Rules for Operator Overloading
- Existing operators can only be overloaded, but the new operators cannot be overloaded.
- The overloaded operator contains atleast one operand of the user-defined data type.
- We cannot use friend function to overload certain operators. However, the member function can be used to overload those operators.
- When unary operators are overloaded through a member function take no explicit arguments, but, if they are overloaded by a friend function, takes one argument.
- When binary operators are overloaded through a member function takes one explicit argument, and if they are overloaded through a friend function takes two explicit arguments.
C++ Operators Overloading Example
Let's see the simple example of operator overloading in C++. In this example, void operator ++ () operator function is defined (inside Test class).
// program to overload the unary operator ++.
- #include <iostream>
- Using namespace std;
- Class Test
- {
- Private:
- Int num;
- Public:
- Test(): num(8){}
- Void operator ++() {
- Num = num+2;
- }
- Void Print() {
- Cout<<"The Count is: "<<num;
- }
- };
- Int main()
- {
- Test tt;
- ++tt; // calling of a function "void operator ++()"
- Tt.Print();
- Return 0;
- }
Output:
The Count is: 10
Let's see a simple example of overloading the binary operators.
// program to overload the binary operators.
- #include <iostream>
- Using namespace std;
- Class A
- {
- Int x;
- Public:
- A(){}
- A(int i)
- {
- x=i;
- }
- Void operator+(A);
- Void display();
- };
- Void A :: operator+(A a)
- {
- Int m = x+a.x;
- Cout<<"The result of the addition of two objects is : "<<m;
- }
- Int main()
- {
- A a1(5);
- A a2(4);
- a1+a2;
- Return 0;
- }
Output:
The result of the addition of two objects is : 9
Operator overloading
Introduction and Need of Operator Overloading
- Operators have fixed meaning and functionalities for primitive data types like int, float, double etc.
- These operators cannot be used for user defined data types like classes.
- But the functionality of these operators can be extended to user defined data types like classes.
- Operator ‘+’ is used to add two integers but we can add two objects by extending the meaning of ‘+’ operator.
- This is called as operator overloading.
- We can overload operators so that they can perform operation on objects.
- Operator overloading provides special meaning to built in operators to perform some specific computation when the operator is used on objects of that class.
- All C++ operators can be overloaded except following :
- Class member access operator(.*)
- Conditional operator(?:)
- Scope resolution operator(::)
- Size of operator(sizeof)
Operator Function
- Operator is overloaded with the help of operator function.
- It defines the operations to be performed by overloaded operator.
- This operator function can be of two types
1. Member operator function
2. Non Member operator function
- Non member operator functions are usually friend functions of class. Operator functions are discussed in preceding section.
Overloading using Member Operator Function
Syntax of an operator function
- Member operator function is a member of the class.
- It is declared in a class as follows :
Return-type operator OP(argument list);
Where OP -> operator to be overloaded.
- Number of parameters in an argument list depends on the type of operator.
Type of Operator | No. Of Arguments |
Unary | 0 |
Binary | 1 |
- Thus according to above table, overloading unary operators using member functions takes no argument and binary operator takes one argument.
- It is defined as follows :
Return-type class-name :: operator OP(argument list)
{
Operations that overloaded operator performs
}
Rules of Operator Overloading
Operators which cannot be overloaded
All C++ operators can be overloaded except following
- Class member access operator(.*)
- Conditional operator(?:)
- Scope resolution operator(::)
- Size of operator(sizeof)
Operators which cannot be overloaded by using Friend Function
Following operators cannot be overloaded using friends
- () Function call operator
- = Assignment operator
- [] subscripting operator
- -> class member access operator
Rules for operator overloading
- Some operators like (assignment)=, (address)& and comma (,) are by default overloaded.
- We cannot create new operators for overloading. Only built in operators can be overloaded.
- We cannot redefine meaning of in built operators i.e we cannot change the meaning +, - etc.
- Precedence and associativity of operators cannot be changed.
- Overloaded operators cannot have default arguments.
- Operators overloaded using member functions takes no argument for unary and one argument for binary operator.
- Operators overloaded using friend functions takes one argument for unary and two arguments for binary operator.
KEY TAKEAWAY
If we create two or more members having the same name but different in number or type of parameter, it is known as C++ overloading. In C++, we can overload:
- Methods,
- Constructors, and
- Indexed properties
It is because these members have parameters only.
Pitfalls of Operator Overloading and Conversion
What is a pitfall?
C++ code that
- Compiles
- Links
- Runs
- Does something different than you expect
Example:
If (-0.5 <= x <= 0.5) return 0;
Pitfall:
If (-0.5 <= x <= 0.5) return 0;
This expression does not test the mathematical condition
-1.5 <= x <= 1.5
Instead, it first computes -0.5 <= x, which is 0 or 1, and then compares the result with 0.5.
Moral: Even though C++ now has a bool type, Booleans are still freely convertible to int.
Since bool->int is allowed as a conversion, the compiler cannot check the validity of expressions. In contrast, the Java compiler would flag this statement as an error.
Constructor pitfalls
Example:
Int main()
{ string a("Hello");
string b();
string c = string("World");
// ...
return 0;
}
Pitfall:
String b();
This expression does not construct an object b of type string. Instead, it is the prototype for a function b with no arguments and return type string.
Moral: Remember to omit the ( ) when invoking the default constructor.
The C feature of declaring a function in a local scope is worthless since it lies about the true scope. Most programmers place all prototypes in header files. But even a worthless feature that you never use can haunt you.
Example:
Template<typename T>
class Array
{
public:
Array(int size);
T& operator[](int);
Array<T>& operator=(const Array<T>&);
// ...
};
int main()
{ Array<double> a(10);
a[0] = 0; a[1] = 1; a[2] = 4;
a[3] = 9; a[4] = 16;
a[5] = 25; a = 36; a[7] = 49;
a[8] = 64; a[9] = 81;
// ...
return 0;
}
Pitfall:
a = 36;
Surprisingly, it compiles:
a = Array<double>(36);
a is replaced with a new array of 36 numbers.
Moral: Constructors with one argument serve double duty as type conversions.
Avoid constructors with a single integer argument! Use the explicit keyword if you can't avoid them.
Example:
Template<typename T>
class Array
{
public:
explicit Array(int size);
// ...
private:
T* _data;
int _size;
};
template<typename T>
Array<T>::Array(int size)
: _size(size),
_data(new T(size))
{}
int main()
{ Array<double> a(10);
a[1] = 64; // program crashes
// ...
}
Pitfall:
Template<typename T>
Array<T>::Array(int size)
: _size(size),
_data(new T(size)) // should have been new T[size]
{}
Why did it compile?
New T(size)
Returns a T* pointer to a single element of type T, constructed from the integer size.
New T[size]
Returns a T* pointer to an array of size objects of type T, constructed with the default constructor.
Moral: Array/pointer duality is dumb, but unfortunately pervasive in C and C++.
The Java compiler would catch this--Java, like most programming languages, supports genuine array types.
Example:
Template<typename T>
class Array
{
public:
explicit Array(int size);
// ...
private:
T* _data;
int _capacity;
int _size;
};
template<typename T>
Array<T>::Array(int size)
: _size(size),
_capacity(_size + 10),
_data(new T[_capacity])
{}
int main()
{ Array<int> a(100);
. . .
// program starts acting flaky
}
Pitfall:
Array<T>::Array(int size)
: _size(size),
_capacity(size + 10),
_data(new T[_capacity])
{}
Initialization follows the member declaration order, not the initializer order!
Array<T>::Array(int size)
: _data(new T[_capacity])
_capacity(_size + 10),
_size(size),
Tip: Do not use data members in initializer expressions.
Array<T>::Array(int size)
: _data(new T[size + 10])
_capacity(size + 10),
_size(size),
Example:
Class Point
{
public:
Point(double x = 0, double y = 0);
// ...
private:
double _x, _y;
};
int main()
{ double a, r, x, y;
// ...
Point p = (x + r * cos(a), y + r * sin(a));
// ...
return 0;
}
Pitfall:
Point p = (x + r * cos(a), y + r * sin(a));
This should be either
Point p(x + r * cos(a), y + r * sin(a));
Or
Point p = Point(x + r * cos(a), y + r * sin(a));
The expression
(x + r * cos(a), y + r * sin(a))
Has a legal meaning. The comma operator discards x + r * cos(a) and evaluates y + r * sin(a). The
Point(double x = 0, double y = 0)
Constructor makes a Point(y + r * sin(a), 0).
Moral: Default arguments can lead to unintended calls. In our case, the construction Point(double) is not reasonable, but the construction Point() is. Only use defaults if all resulting call patterns are meaningful.
Example:
Class Shape
{
public:
Shape();
private:
virtual void reset();
Color _color;
};
class Point : public Shape
{
public:
// ...
private:
double _x, _y;
};
void Shape::reset() { _color = BLACK; }
void Point::reset()
{ Shape::reset();
_x = 0; _y = 0;
}
Shape::Shape() { reset(); }
There is no Point constructor--we use the virtual function in the Shape constructor.
Pitfall:
Shape::Shape() { reset(); }
Point p;
When constructing Point, the Shape::reset(), not the Point::reset() virtual function is called. Why?
Explanation: Virtual functions do not work in constructors.
The Shape subobject is constructed before thePoint object. Inside the Shape constructor, the partially constructed object is still a Shape.
Example:
Class Shape // an abstract class
{
public:
Shape();
private:
void init();
virtual void reset() = 0;
Color _color;
};
Shape::Shape() { init(); }
void Shape::init() { reset(); }
class Point : public Shape // a concrete derived class
{
public:
virtual void reset();
// ...
private:
double _x, _y;
};
void Point::reset() { _x = _y = 0; }
Pitfall:
Int main()
{ Point p; // program crashes
return 0;
}
Explanation: You cannot create an instance of an abstract class (a class with a pure, = 0, virtual function).
Shape s; // compile-time error; Shape is abstract
That's a good thing: if you could, what would happen if you called
s.reset(); // reset not defined for shapes
But...I lied. You can create instances of abstract classes.
When constructing a concrete derived, for a fleeting moment, the base class exists. If you invoke a pure virtual function before the derived class constructor has executed, the program terminates.
Destructor pitfalls
Example:
Class Employee
{
public:
Employee(string name);
virtual void print() const;
private:
string _name;
};
class Manager : public Employee
{
public:
Manager(string name, string dept);
virtual void print() const;
private:
string _dept;
};
int main()
{ Employee* staff[10];
staff[0] = new Employee("Harry Hacker");
staff[1] = new Manager("Joe Smith", "Sales");
// ...
for (int i = 0; i < 10; i++)
staff[i]->print();
for (int i = 0; i < 10; i++)
delete staff[i];
return 0;
}
Where is the memory leak?
Pitfall:
Delete staff[i];
Destroys all objects with ~Employee(). The _dept strings of the Manager objects are never destroyed.
Moral: A class from which you derive must have a virtual destructor.
Example:
Class Employee
{
public:
Employee(string name);
virtual void print() const;
virtual ~Employee(); // <-----
private:
string _name;
};
class Employee
{
public:
Employee(string name);
private:
string _name;
};
class Manager
{
public:
Manager(string name, string sname);
~Manager();
private:
Employee* _secretary;
}
Manager::Manager(string name, string sname)
: Employee(name),
_secretary(new Employee(sname))
{}
Manager::~Manager() { delete _secretary; }
What is wrong with the Manager class?
Pitfall:
Int main()
{ Manager m1 = Manager("Sally Smith",
"Joe Barnes");
Manager m2 = m1;
// ...
}
The destructors of both m1 and m2 will delete the same Employee object.
Moral: A class with a destructor needs a copy constructor
Manager::Manager(const Manager&)
And an assignment operator
Manager& Manager::operator=(const Manager&).
The Big 3: It's not just a good idea--it's the law (Marshall Cline)
Inheritance pitfalls
Example:
Class Employee
{
public:
Employee(string name, string dept);
virtual void print() const;
string dept() const;
private:
string _name;
string _dept;
};
class Manager : public Employee
{
public:
Manager(string name, string dept);
virtual void print() const;
private:
// ...
};
void Employee::print() const
{ cout << _name << endl;
}
void Manager::print() const
{ print(); // print base class
cout << dept() << endl;
}
Pitfall:
Void Manager::print() const
{ print(); // print base class
cout << dept() << endl;
}
Despite what the comment says,print() selects the print operation of the Manager class. In contrast, dept() selects the operation of the Employee class sinceManager does not redefine it.
Moral: When calling a base class operation in a derived class operation of the same name, use scope resolution:
Void Manager::print() const
{ Employee::print(); // print base class
cout << dept() << endl;
}
Example:
Void Manager::print() const
{ Employee :print(); // print base class
cout << dept() << endl;
}
Pitfall:
Employee:print();
It should be
Employee::print();
But why does it compile? Employee: is a goto label!
Moral:Even language features that you never use can bite you!
Example:
Class Employee
{
public:
void raise_salary(double by_percent);
// ...
};
class Manager : public Employee
{
public:
// ...
};
void make_them_happy(Employee* e, int ne)
{ for (int i = 0; i < ne; i++)
e[i].raise_salary(0.10);
}
int main()
{ Employee e[20];
Manager m[5];
m[0] = Manager("Joe Bush", "Sales");
// ...
make_them_happy(e, 20);
make_them_happy(m + 1, 4); // let's skip Joe
return 0;
}
Pitfall:
Void make_them_happy(Employee* e, int ne);
Manager m[5];
make_them_happy(m + 1, 4);
Why does it compile?
The type of m + 1 is Manager*. Because of inheritance, a Manager* is convertible to an Employee* base class pointer. Make_them_happyreceives an Employee*. Everyone is happy.
What is the problem?
The array computation e[i] computes an offset of i*sizeof(Employee).
Moral: Pointers are overused in C++. Here we see two interpretations of an Employee* e.
- e points to either an Employee or a derived class object, such as a Manager.
- e points to either an Employee or a bunch of Employee objects, stacked up in an array.
These two interpretations are incompatible. Mixing them leads to runtime errors. However, the intention of the programmer is hidden to the compiler since both ideas are expressed by the same construct--a pointer.
Example:
Class Employee
{
public:
Employee(char name[]);
Employee(const Employee& b);
~Employee();
Employee& operator=(const Employee& b);
. . .
private:
char* _name;
string _dept;
};
class Manager : public Employee
{
public:
Manager(char name[], char dept[]);
Manager(const Manager& b);
~Manager();
Manager& operator=(const Manager& b);
. . .
private:
char* _dept;
};
Manager::Manager(const Manager& b)
: _dept(new char[strlen(b._dept) + 1])
{ strcpy(b._dept, _dept);
}
Manager::~Manager()
{ delete[] _dept;
}
Manager& Manager::operator=(const Manager& b)
{ if (this == &b) return *this;
delete[] _dept;
_dept = new char[strlen(b._dept) + 1];
strcpy(b._dept, _dept);
return *this;
}
Pitfall:
Manager& Manager::operator=(const Manager& b)
{ if (this == &b) return *this;
delete[] _dept;
_dept = new char[strlen(b._dept) + 1];
strcpy(b._dept, _dept);
return *this;
}
Constructors and destructors automatically call the base constructors and destructors. But operator= does not automatically invoke the operator= of the base class.
Moral: When redefining operator= in a derived class, explicitly call operator= of the base class:
Manager& Manager::operator=(const Manager& b)
{ if (this == &b) return *this;
Employee::operator=(b);
Delete[] _dept;
_dept = new char[strlen(b._dept) + 1];
Strcpy(b._dept, _dept);
Return *this;
}
Stream pitfalls
Example:
List<int> a;
while (!cin.eof())
{ int x;
cin >> x;
if (!cin.eof()) a.push_back(x);
}
Pitfall:
While (!cin.eof())
{ // ...
}
This may be an infinite loop. If the stream state turns to fail, the end of file will never be reached.
The stream state will be set to fail if a non-digit is encountered when trying to read an integer.
Moral:eof() is only useful in combination withfail(), to find out whether EOF was the cause for failure
Example:
While (cin.good())
{ int x;
cin >> x;
if (cin.good()) a.push_back(x);
}
Pitfall:
Cin>> x; // <--- may succeed and then encounter EOF
if (cin.good()) a.push_back(x);
This code may miss the last element in the input file, if it is directly followed by EOF.
Remedy: Use fail():
While (!cin.fail())
{ int x;
cin >> x;
if (!cin.fail()) a.push_back(x);
}
The type conversion basic_ios ----> void* is identical to !fail():
While (cin)
{ int x;
cin >> x;
if (cin) a.push_back(x);
}
Moral: There are four stream test functions:good(), bad(), eof(), and fail(). (Note thatbad() does not mean !good().) Only one of them is useful: fail().
Overloading pitfalls
Example:
Class Complex
{
public:
Complex(double = 0, double = 0);
Complex operator+(Complex b) const;
Complex operator-(Complex b) const;
Complex operator*(Complex b) const;
Complex operator/(Complex b) const;
Complex operator^(Complex b) const;
// ...
private:
double _re, _im;
};
int main()
{ Complex i(0, 1);
cout << i^2 + 1; // i*i is -1
return 0;
}
Why won't it print (0,0)?
Pitfall:
Cout<< i^2 + 1;
Using the C/C++ operator precedence rules, we can add parentheses:
Cout<< (i ^ (2 + 1));
The ^ operator is weaker than + (but stronger than <<).
Moral: You cannot change the operator precedence when overloading operators. Do not overload an operator if its precedence is not intuitive for the problem domain.
The precedence of ^ is fine for XOR but not for raising to a power.
Example: The stream classes support a type conversion basic_ios ----> void* for testing if a stream is happy:
While (cin)
{ int x;
cin >> x;
// ...
}
Why convert to void*? A conversion to bool would seem to make more sense.
template<typename C, typename T = char_traits<C>>
class basic_ios
{
public:
// ...
operator bool() const
{ if (fail()) return false;
else return true;
}
private:
// ...
};
Pitfall:
While (cin)
{ int x;
cin << x;
// ...
}
Note the typo--it should be cin >> x.
But cin << x has an unintended meaning:cin.operator bool(), converted to an int and shifted by x bits.
Moral: Use conversion to void*, not conversion to int or bool, to implement objects yielding truth values. Unlike int or bool, void* have no legal operations other than == comparison.
Example: An array class with a [] operator that grows the array on demand
Class Array
{
public:
Array();
~Array();
Array(const Array&);
Array& operator=(const Array&);
int& operator[](int);
private:
int _n; // current number of elements
int* _a; // points to heap array
};
int& Array::operator[](int i)
{ if (i > _n)
{ int* p = new int[i];
for (int k = 0; k < _n; k++)
p[k] = _a[k];
for (; k < i; k++) p[k] = 0;
delete[] _a;
_a = p;
_n = i;
}
return _a[i];
}
int main()
{ Array a;
for (int s = 1; s <= 100; s++)
a[s] = s * s;
return 0;
}
Pitfall:
Void swap(int& x, int& y)
{ int temp = x;
x = y;
y = temp;
}
int main()
{ Array a;
a[3] = 9;
swap(a[3], a[4]);
return 0;
}
The swap function gets references to a[3], then to a[4], but the second computation moves the array and invalidates the first reference!a[4] is swapped with a wild reference.
Moral: You cannot simultaneously relocate a memory block and export a reference to it.
Either make [] not grow the array, or use a data structure in which elements never move (i.e. a sequence of chunks, such as in std::deque).
Exception pitfalls
Example:
Void read_stuff(const char filename[])
{ FILE* fp = fopen(filename, "r");
do_reading(fp);
fclose(fp);
}
Why is that an "exception pitfall"? There aren't any exceptions in the code!
Pitfall:
FILE* fp = fopen(filename, "r");
do_reading(fp);
fclose(fp); // <-- may never get here
If do_reading throws an exception, or calls a function that throws an exception, it never comes back!fp is never closed.
Moral: Exception handling drastically alters control flow. You cannot take it for granted that a function ever returns.
Remedy 1: (popular but dumb)
Void read_stuff(const char filename[])
{ FILE* fp = fopen(filename, "r");
try
do_reading(fp);
catch(...)
{ fclose(fp);
throw;
}
fclose(fp);
}
Remedy 2: (smart)
Void read_stuff(const char filename[])
{ fstream fp(filename, ios_base::in);
do_reading(fp);
}
Even if do_reading throws an exception, fp is closed by the ifstream destructor.
Moral: In code with exception handling (i.e. all C++ code starting in 1994), relinquish resources only in destructors!
Example:
Double find_salary_increase(auto_ptr<Employee>);
void do_stuff(const char name[])
{ auto_ptr<Employee> pe = new Employee(name);
// can't use
// Employee* pe = new Employee(name)
// that's not not exception safe
double rate = find_salary_increase(pe);
pe->raise_salary(rate);
}
Pitfall:
Find_salary_increase(pe);
Invokes the copy constructor of auto_ptr<Employee> which transfers ownership to the copy.
Only one auto_ptr can own a heap object. The owning auto_ptr calls the destructor when it goes out of scope.
Remedy: Don't copy an auto_ptr into a function.
Double find_salary_increase(Employee*);
void do_stuff(const char name[])
{ Employee* pe = new Employee(name);
auto_ptr<Employee> ape = pe; // use only for destruction
double rate = find_salary_increase(pe);
pe->raise_salary(rate);
}
Container pitfalls
Example: A set of pointers
Set<Employee*> staff;
vector<string> names;
for (int i = 0; i < names.size(); i++)
staff.insert(new Employee(names[i]);
Pitfall: Ordered containers (set, map, multiset, multimap) use < for comparison. It is assumed that < is a total ordering.
In a set of pointers
Set<Employee*> staff;
The pointers are compared with <.
Given two arbitrary Employee* pointers p and q, is p < q defined? Only if they point to the same array.
In a segmented memory model, only offsets are compared. Ex. p == 0x740A0004 and q == 0x7C1B0004 compare identical.
Remedy: (risky) Only write code for a flat memory space where pointer word size == integer word size and comparison happens to be a total ordering.
Remedy: (tedious) Supply an ordering:
Bool employee_ptr_less(const Employee* a, const Employee* b)
{ return a->salary() < b->salary();
}
set<Employee*, bool (*)(const Employee*, const Employee*)>
staff(employee_ptr_less);
Example: Runaway iterators
List<int> a, b;
// ...
list<int>::iterator p
= find(a.begin(), b.end(), 100);
if (p != a.end()) b.push_back(*p);
Pitfall:
Find(a.begin(), b.end(), 100); // oops, should have been a.end()
To see why this code crashes dramatically, look at the implementation of find:
Template<typename I, typename T>
I find(I from, I to, const T& target)
{ while (from != to && *from != target)
++from;
return from;
}
When from reaches a.end(), *from and ++from are undefined.
Moral: Iterators don't know their state. There is no reason why a list<int> iterator couldn't know its state, but STL was built with the objective to make iterators no more powerful than pointers into a C array. Benefit: You can call the standard algorithms with C arrays:
Int a[30];
int* p = find(a, a + 30, 100);
Drawback: Programming with iterators is pointer-oriented, not object-oriented.
Example: Muddled iterators
List<int> a;
list<int> b;
list<int>::iterator p = a.begin();
a.insert(50);
b.insert(100);
b.erase(p);
cout << a.length() << endl; // length is 1
cout << b.length() << endl; // length is 0
Pitfall: In
b.erase(p);
The iterator p pointed inside a! The behavior is undefined, but the standard STL implementation does the following:
- *p is erased from whatever list it happens to be in, just by following the forward and backwards link
- The length of b is decremented
Moral: Iterators don't know their owner.
KEY TAKEAWAY
What is a pitfall?
C++ code that
- Compiles
- Links
- Runs
- Does something different than you expect
C++ allows you to specify more than one definition for a function name or an operator in the same scope, which is called function overloading and operator overloading respectively.
An overloaded declaration is a declaration that is declared with the same name as a previously declared declaration in the same scope, except that both declarations have different arguments and obviously different definition (implementation).
When you call an overloaded function or operator, the compiler determines the most appropriate definition to use, by comparing the argument types you have used to call the function or operator with the parameter types specified in the definitions. The process of selecting the most appropriate overloaded function or operator is called overload resolution.
Function Overloading in C++
You can have multiple definitions for the same function name in the same scope. The definition of the function must differ from each other by the types and/or the number of arguments in the argument list. You cannot overload function declarations that differ only by return type.
Following is the example where same function print() is being used to print different data types −
#include <iostream>
Using namespace std;
Class printData {
Public:
Void print(int i) {
Cout << "Printing int: " << i << endl;
}
Void print(double f) {
Cout << "Printing float: " << f << endl;
}
Void print(char* c) {
Cout << "Printing character: " << c << endl;
}
};
Int main(void) {
PrintData pd;
// Call print to print integer
Pd.print(5);
// Call print to print float
Pd.print(500.263);
// Call print to print character
Pd.print("Hello C++");
Return 0;
}
When the above code is compiled and executed, it produces the following result −
Printing int: 5
Printing float: 500.263
Printing character: Hello C++
Operators Overloading in C++
You can redefine or overload most of the built-in operators available in C++. Thus, a programmer can use operators with user-defined types as well.
Overloaded operators are functions with special names: the keyword "operator" followed by the symbol for the operator being defined. Like any other function, an overloaded operator has a return type and a parameter list.
Box operator+(const Box&);
Declares the addition operator that can be used to add two Box objects and returns final Box object. Most overloaded operators may be defined as ordinary non-member functions or as class member functions. In case we define above function as non-member function of a class then we would have to pass two arguments for each operand as follows −
Box operator+(const Box&, const Box&);
Following is the example to show the concept of operator over loading using a member function. Here an object is passed as an argument whose properties will be accessed using this object, the object which will call this operator can be accessed using this operator as explained below −
#include <iostream>
Using namespace std;
Class Box {
Public:
Double getVolume(void) {
Return length * breadth * height;
}
Void setLength( double len ) {
Length = len;
}
Void setBreadth( double bre ) {
Breadth = bre;
}
Void setHeight( double hei ) {
Height = hei;
}
// Overload + operator to add two Box objects.
Box operator+(const Box& b) {
Box box;
Box.length = this->length + b.length;
Box.breadth = this->breadth + b.breadth;
Box.height = this->height + b.height;
Return box;
}
Private:
Double length; // Length of a box
Double breadth; // Breadth of a box
Double height; // Height of a box
};
// Main function for the program
Int main() {
Box Box1; // Declare Box1 of type Box
Box Box2; // Declare Box2 of type Box
Box Box3; // Declare Box3 of type Box
Double volume = 0.0; // Store the volume of a box here
// box 1 specification
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// box 2 specification
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// volume of box 1
Volume = Box1.getVolume();
Cout << "Volume of Box1 : " << volume <<endl;
// volume of box 2
Volume = Box2.getVolume();
Cout << "Volume of Box2 : " << volume <<endl;
// Add two object as follows:
Box3 = Box1 + Box2;
// volume of box 3
Volume = Box3.getVolume();
Cout << "Volume of Box3 : " << volume <<endl;
Return 0;
}
When the above code is compiled and executed, it produces the following result −
Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400
Over loadable/Non-over loadable Operators
Following is the list of operators which can be overloaded −
+ | - | * | / | % | ^ |
& | | | ~ | ! | , | = |
< | > | <= | >= | ++ | -- |
<< | >> | == | != | && | || |
+= | -= | /= | %= | ^= | &= |
|= | *= | <<= | >>= | [] | () |
-> | ->* | New | New [] | Delete | Delete [] |
Following is the list of operators, which can not be overloaded −
:: | .* | . | ?: |
A member function of a class is a function that has its definition or its prototype within the class definition like any other variable. It operates on any object of the class of which it is a member, and has access to all the members of a class for that object.
Let us take previously defined class to access the members of the class using a member function instead of directly accessing them −
Class Box {
Public:
Double length; // Length of a box
Double breadth; // Breadth of a box
Double height; // Height of a box
Double getVolume(void);// Returns box volume
};
Member functions can be defined within the class definition or separately using scope resolution operator, : −. Defining a member function within the class definition declares the function inline, even if you do not use the inline specifier. So either you can define Volume() function as below −
Class Box {
Public:
Double length; // Length of a box
Double breadth; // Breadth of a box
Double height; // Height of a box
Double getVolume(void) {
Return length * breadth * height;
}
};
If you like, you can define the same function outside the class using the scope resolution operator (::) as follows −
Double Box::getVolume(void) {
Return length * breadth * height;
}
Here, only important point is that you would have to use class name just before :: operator. A member function will be called using a dot operator (.) on a object where it will manipulate data related to that object only as follows −
Box myBox; // Create an object
MyBox.getVolume(); // Call member function for the object
Let us put above concepts to set and get the value of different class members in a class −
#include <iostream>
Using namespace std;
Class Box {
Public:
Double length; // Length of a box
Double breadth; // Breadth of a box
Double height; // Height of a box
// Member functions declaration
Double getVolume(void);
Void setLength( double len );
Void setBreadth( double bre );
Void setHeight( double hei );
};
// Member functions definitions
Double Box::getVolume(void) {
Return length * breadth * height;
}
Void Box::setLength( double len ) {
Length = len;
}
Void Box::setBreadth( double bre ) {
Breadth = bre;
}
Void Box::setHeight( double hei ) {
Height = hei;
}
// Main function for the program
Int main() {
Box Box1; // Declare Box1 of type Box
Box Box2; // Declare Box2 of type Box
Double volume = 0.0; // Store the volume of a box here
// box 1 specification
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// box 2 specification
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// volume of box 1
Volume = Box1.getVolume();
Cout << "Volume of Box1 : " << volume <<endl;
// volume of box 2
Volume = Box2.getVolume();
Cout << "Volume of Box2 : " << volume <<endl;
Return 0;
}
When the above code is compiled and executed, it produces the following result −
Volume of Box1 : 210
Volume of Box2 : 1560
C++ Friend Functions
A friend function of a class is defined outside that class' scope but it has the right to access all private and protected members of the class. Even though the prototypes for friend functions appear in the class definition, friends are not member functions.
A friend can be a function, function template, or member function, or a class or class template, in which case the entire class and all of its members are friends.
To declare a function as a friend of a class, precede the function prototype in the class definition with keyword friend as follows −
Class Box {
Double width;
Public:
Double length;
Friend void printWidth( Box box );
Void setWidth( double wid );
};
To declare all member functions of class ClassTwo as friends of class ClassOne, place a following declaration in the definition of class ClassOne −
Friend class ClassTwo;
Consider the following program −
#include <iostream>
Using namespace std;
Class Box {
Double width;
Public:
Friend void printWidth( Box box );
Void setWidth( double wid );
};
// Member function definition
Void Box::setWidth( double wid ) {
Width = wid;
}
// Note: printWidth() is not a member function of any class.
Void printWidth( Box box ) {
/* Because printWidth() is a friend of Box, it can
Directly access any member of this class */
Cout << "Width of box : " << box.width <<endl;
}
// Main function for the program
Int main() {
Box box;
// set box width without member function
Box.setWidth(10.0);
// Use friend function to print the wdith.
PrintWidth( box );
Return 0;
}
When the above code is compiled and executed, it produces the following result −
Width of box : 10
KEY TAKEAWAY
C++ allows you to specify more than one definition for a function name or an operator in the same scope, which is called function overloading and operator overloading respectively.
An overloaded declaration is a declaration that is declared with the same name as a previously declared declaration in the same scope, except that both declarations have different arguments and obviously different definition (implementation).
When you call an overloaded function or operator, the compiler determines the most appropriate definition to use, by comparing the argument types you have used to call the function or operator with the parameter types specified in the definitions. The process of selecting the most appropriate overloaded function or operator is called overload resolution.
Member operator function takes no parameter for unary operator and is implicitly passed through “this” pointer.
Overloading Unary Minus(–)
Program
Write a program in C++ to overload unary minus(-).
Solution :
#include<iostream> Using namespace std ; Class Example { Int x,y,z; Public:
Example(int p,int q,int r) { x=p; y=q; z=r; } Void display() { Cout<<x<<” ”<<y<<” ”<<z; } Void operator-() { x=-x; y=-y; z=-z; } }; Int main() { Example obj (10,-20,30); Cout<<”Original numbers are” Obj.display(); - obj; Obj.display(); Return 0; } |
//Defines class Example
//Parameterized constructor
// Step 1 Operator function to overload minus
// Step 2 Invokes operator function
Cout<<”Numbers after applying unary minus” |
Explanation
- This program overloads unary – on data members of class Example.
- It defines operator function to overload unary – so that it can be applied to object. See step 1.
Void operator-() { x=-x; y=-y; z=-z; } | //Operator function to overload minus
|
- This operator function is invoked by applying unary – on object ‘obj’. See step 2.
- obj; Cout<<”Numbers after applying unary minus” | //Invokes operator function |
Output
Original numbers are
10 -20 30
Numbers after applying unary minus
-10 20 -30
KEY TAKEAWAY
Member operator function takes no parameter for unary operator and is implicitly passed through “this” pointer.
Overloading Unary Minus(–)
- Member operator function takes only one parameter for binary operator as left hand side operand is implicitly passed through this pointer.
- Left hand side object is responsible to invoke operator function.
- Overloading plus(+) and minus(–)
- We can perform addition and subtraction on two objects by overloading + and -.
Program
Write a C++ program to overload binary +, - .
#include<iostream> Using namespace std ; Class Example { Int a,b; Public: Example(int p, int q) { a=p; b=q; } Void display() { Cout<<a<<” ”<<b<<endl; } Example operator+(Example X); Example operator-(Example X); }; Example Example::operator+(Example X) { Example E; E.a= a+X.a; E.b= b+X.b; Return E } Example Example:: operator-(Example X) { Example F; F.a= a-X.a; F.b= b-X.b; Return F } Int main() { Example obj1(10,20); Example obj2(5,5); Example obj3(0,0); Cout<<”Addition is” Obj3= obj1+ obj2; Obj3.display(); Cout<<”Subtraction is” Obj3= obj1- obj2; Obj3.display(); Return 0 ; } |
// Step 1 overloads + // Step 1 overloads -
// Step 2 Defines operator function to overload +
//Defines operator function to overload -
// Step 3 //Invokes overloaded binary +
// Step4 //Invokes overloaded binary -
|
Explanation
- This program overloads binary + and -. For this operator functions are declared in class. See step 1.
- This operator function returns objects which stores addition of two objects.
Return type of an object is always a class so it is declared as follows
Example operator+(Example X); Example operator-(Example X); | //overloads + //overloads - |
Operator functions are defined outside class using scope resolution operator. See step 2.
Example Example:: operator+(Example X) { Example E; E.a= a+X.a; E.b= b+X.b; Return E; } Example Example:: operator-(Example X) { Example F; F.a= a-X.a; F.b= b-X.b; Return F;
} | //Defines operator function to overload +
//Defines operator function to overload -
|
- Operator function takes only one argument as left hand side operand is passed implicitly via this pointer.
- This operator function returns object where addition is stored.
- Return type of an object is always a class so it is declared as follows :
- Operator function for addition is activated when two objects are added using +. See step 3.
O3= O1+ O2;
| //Invokes overloaded binary + |
- Operator function for subtraction is activated when two objects are subtracted using -. See step 4.
O3= O1- O2; | //Invokes overloaded binary - |
Thus the output will be :
Output
Cout<<”Addition is”
a 15 b 25
Cout<<”Subtraction is”
a 5 b 15
Program
Write C++ program to demonstrate use of binary (“+”) operator overloading to add two complex numbers using member function.
#include<iostream> Using namespace std ; Class Demo { Int real,imag; Public: Demo(int p, int q) { Real=p; Imag=q; } Void display() { Cout<<real<<” +j”<<imag<<endl; } Demo operator+(Demo X); }; Demo Demo::operator+(Demo X) { Demo D; D.real=real+X.real; D.imag=imag+D.imag; Return D } Int main() { Demo obj1(10,20); Demo obj2(5,5); Demo obj3(0,0); Cout<<”Addition is” Obj3= obj1+ obj2; Obj3.display(); Return 0 ; } |
//……1overloads +
//.2 Defines operator function to overload +
//Defines operator function to overload -
//…..3 //Invokes overloaded binary +
|
KEY TAKEAWAY
Member operator function takes only one parameter for binary operator as left hand side operand is implicitly passed through this pointer.
Left hand side object is responsible to invoke operator function.
- Friend function using operator overloading offers better flexibility to the class.
- These functions are not a members of the class and they do not have 'this' pointer.
- When you overload a unary operator you have to pass one argument.
- When you overload a binary operator you have to pass two arguments.
- Friend function can access private members of a class directly.
Syntax:
Friend return-type operator operator-symbol (Variable 1, Varibale2)
{
//Statements;
}
Example : Program demonstrating Unary operator overloading using Friend function
#include<iostream>
using namespace std;
class UnaryFriend
{
int a=10;
int b=20;
int c=30;
public:
void getvalues()
{
cout<<"Values of A, B & C\n";
cout<<a<<"\n"<<b<<"\n"<<c<<"\n"<<endl;
}
void show()
{
cout<<a<<"\n"<<b<<"\n"<<c<<"\n"<<endl;
}
void friend operator-(UnaryFriend &x); //Pass by reference
};
void operator-(UnaryFriend &x)
{
x.a = -x.a; //Object name must be used as it is a friend function
x.b = -x.b;
x.c = -x.c;
}
int main()
{
UnaryFriend x1;
x1.getvalues();
cout<<"Before Overloading\n";
x1.show();
cout<<"After Overloading \n";
-x1;
x1.show();
return 0;
}
Output:
Values of A, B & C
10
20
30
Before Overloading
10
20
30
After Overloading
-10
-20
-30
In the above program, operator – is overloaded using friend function. The operator() function is defined as a Friend function. The statement -x1 invokes the operator() function. The object x1 is created of class UnaryFriend. The object itself acts as a source and destination object. This can be accomplished by sending reference of an object. The object x1 is a reference of object x. The values of object x1 are replaced by itself by applying negation.
KEY TAKEAWAY
- Friend function using operator overloading offers better flexibility to the class.
- These functions are not a members of the class and they do not have 'this' pointer.
- When you overload a unary operator you have to pass one argument.
- When you overload a binary operator you have to pass two arguments.
- Friend function can access private members of a class directly.
References:
1. E Balagurusamy, “Programming with C++”, Tata McGraw Hill, 3rd Edition.
2. Herbert Schildt, “The Complete Reference C++”, 4th Edition.
3. Robert Lafore, “Object Oriented Programming in C++”, Sams Publishing, 4th Edition.
4. Matt Weisfeld, “The Object-Oriented Thought Process”, Pearson Education.