Operator Overloading in C++ Questionspammann/332/ppt/kak/slides12b.pdf · Operator Overloading in...
Transcript of Operator Overloading in C++ Questionspammann/332/ppt/kak/slides12b.pdf · Operator Overloading in...
Operator Overloading in C++
Questions:
1
1. Every operator has three properties associated
with it. What are those?
2
2. Which of the following statements are
true, if any?
a) You can use operator overloading to change
the predefined arity of an operator.
b) You can use operator overloading to change
the predefined precedence of an operator.
c) You can change operator overloading to
change the associativity of an operator.
3
3. When you overload an operator, at least one
of the operands must be of which type?
4
4. What is the difference between an ‘‘operator
token’’ and an ‘‘operator function’’?
5
5. What operator function is associated with
the operator +?
6
6. In terms of the operator functions associated
with the operator tokens, how does the compiler
interpret the following statement for class
type operands?
x1 + x2;
7
7. Let’s say that overload definition for the
operator function
operator+()
is supplied as a global function, how would
the compiler interpret the statement
x1 + x2;
8
8. If the overload definition for the operator
function
operator+()
is supplied as a member function, how would
the compiler interpret the statement
x1 + x2;
9
9. For programmer-defined classes, why is it
that we cannot overload the output and the
input operators as member functions of the
class?
10
Member-Function Overload Definitions for Operators
11
//MyComplexMem.cc
#include <iostream>
using namespace std;
class MyComplex {
double re, im;
public:
MyComplex( double r, double i ) : re(r), im(i) {}
MyComplex operator+( MyComplex) const;
MyComplex operator-( MyComplex) const;
// ostream& operator<< ( ostream& os ); WRONG
// ostream& operator<<( ostream&, MyComplex& ); WRONG
friend ostream& operator<< ( ostream&, const MyComplex& );
};
MyComplex MyComplex::operator+( const MyComplex arg ) const
{
double d1 = re + arg.re;
double d2 = im + arg.im;
return MyComplex( d1, d2 );
}
MyComplex MyComplex::operator-( const MyComplex arg ) const
{
double d1 = re - arg.re;
double d2 = im - arg.im;
return MyComplex( d1, d2 );
}
/*
ostream& MyComplex::operator<< ( ostream& os ) // WRONG
{
12
os << "Real part: " << re << " Imag part: " << im << endl;
return os;
}
ostream& MyComplex::operator<< ( ostream& os, MyComplex c ) // WRONG
{
os << "Real part: " << c.re << " Imag part: " << c.im << endl;
return os;
}
*/
ostream& operator<< ( ostream& os, const MyComplex& c )
{
os << "(" << c.re << ", " << c.im << ")" << endl;
return os;
}
int main()
{
MyComplex first(3, 4);
MyComplex second(2, 9);
cout << first; // (3, 4)
cout << second; // (2, 9)
cout << first + second; // (5, 13)
cout << first - second; // (1, -5)
}
13
Complex MyComplex::operator+( const MyComplex arg ) const
{
double d1 = re + arg.re;
double d2 = im + arg.im;
return MyComplex( d1, d2 );
}
MyComplex c1( 3, 5);
MyComplex c2( 2, 4);
MyComplex result = c1.operator+( c2 );
14
MyComplex result = c1 + c2;
c1 + c2
the left object is the invoking object.
15
In general, a construct such as
object1 χ object2
in which χ is a member-function overloaded operator,
is interpreted by the compiler as
object1.operator χ ( object2 )
implying that object1 is the invoking object for the operator χ.
16
On the other hand, if the operator χ was overloaded with a global defini-
tion, the construct
object1 χ object2
would be interpreted by the compiler as
operatorχ ( object1, object2 )
17
This implies that the global overloaded operator
ostream& operator<< ( ostream& os, const Complex& arg )
{
double d1 = arg.getReal();
double d2 = arg.getImag();
os << "Real part: " << d1 << " Imag part: " << d2 << endl;
return os;
}
cannot be replaced by something like
ostream& Complex::operator<< ( ostream& os ) { //
os << "Real part: " << re << " Imag part: " << im << endl;
return os;
}
or by something like this
ostream& Complex::operator<< ( ostream& os, Complex c ) { //
os << "Real part: " << c.re << " Imag part: " << c.im <<
return os;
}
18
Since the output stream operator << is used in the manner
cout << expression;
which means that the left object will be the invoking object for this oper-
ator if it were defined for member-function overloading.
19
friend ostream& operator<< ( ostream&, Complex& );
This declaration, which can be either in the private section or in the public
section, allows us to define the overloading by
ostream& operator<< ( ostream& os, Complex& c )
{
os << "Real part: " << c.re << " Imag part: " << c.im <<
return os;
}
It is important to note that if we had not declared operator<< to be a
friend of Complex, the body of this function would have had no access to
the re and im members of the parameter c, being private as they are.
20
Global Overload Definitions for Unary Operators
Just as is the case with binary operators, if the arity of an operator is unary
with respect to built-in types, then such an operator can be overloaded
for the user-defined types.
As before, this overloading may be accomplished either by supplying a
global overload definition or a member-function overload definition.
21
Consider again the case of the MyComplex class, with just the unary oper-
ator ’-’ defined this time, and the previously presented overloaded binary
operator << so that we can see the results of applying the unary operator
to a MyComplex type.
22
#include <iostream.h>
using namespace std;
class MyComplex {
double re, im;
public:
MyComplex( double r, double i ) : re(r), im(i) {}
double getReal() const { return re; }
double getImag() const { return im; }
};
// global overload definition for "-" as a unary operator
MyComplex operator-( const MyComplex arg )
{
return MyComplex( -arg.getReal(), -arg.getImag() );
}
// global overload definition for "<<" as a binary operator
ostream& operator<< ( ostream& os, const MyComplex& arg )
{
double d1 = arg.getReal();
double d2 = arg.getImag();
os << "(" << arg.getReal() << ", " << arg.getImag() << ")" << endl;
return os;
}
int main()
{
MyComplex c(3, 4);
cout << c; // (3, 4)
cout << -c; // (-3, -4)
}
23
Member-Function Overload Definitions for Unary Operators
24
#include <iostream.h>
using namespace std;
class MyComplex {
double re, im;
public:
MyComplex( double r, double i ) : re(r), im(i) {}
MyComplex operator-() const;
friend ostream& operator<< ( ostream&, MyComplex& );
};
//Member-function overload definition for "-"
MyComplex MyComplex::operator-() const
{
return MyComplex( -re, -im );
}
//This overload definition has to stay global
ostream& operator<< ( ostream& os, MyComplex& c )
{
os << "(" << c.re << ", " << c.im << ")" << endl;
return os;
}
int main()
{
MyComplex c(3, 4);
MyComplex z = -c;
cout << z << endl; // (-3, -4)
}
25
The statement
Complex z = -c;
is translated by the compiler into
Complex z = c.operator-();
which makes c the invoking object. Thus the values of re and im available
inside the definition of the operator- function would then correspond to
the Complex number c.
26
A Case Study in Operator Overloading
class MyString {
char* charArr;
int length;
class Err {};
public:
//...
};
27
THE MAIN CONSTRUCTOR:
MyString::MyString( char* ch ) {
length = strlen( ch );
charArr = new char[ length + 1];
strcpy( charArr, ch );
}
MyString name("hello");
28
NO-ARG CONSTRUCTOR:
MyString s;
MyString words[100];
vector<MyString> vec(100);
MyString::MyString() {charArr = 0; length = 0;}
29
THE DESTRUCTOR:
MyString::~MyString() { delete[] charArr; }
30
THE COPY CONSTRUCTOR:
MyString s1( "hello" );
MyString s2 = s1;
MyString::MyString( const MyString& str ) {
length=str.length;
charArr = new char[length+1];
strcpy( charArr, str.charArr );
}
MyString s2 = s1;
MyString s1("hello");
31
OVERLOADING OF THE ’=’ OPERATOR:
MyString s1( "hello" );
MyString s2( "jellybeans" );
s1 = s2; // need an assignment operator for this to work
MyString& MyString::operator=( const MyString& str ) {
if (str.charArr == 0) {
delete[] charArr;
charArr = 0;
length = 0;
return *this;
}
if (this != &str) {
delete[] charArr;
charArr = new char[str.length + 1];
strcpy(charArr, str.charArr );
length = str.length;
}
return *this;
}
32
MyString s1("hello");
MyString s2;
s1 = s2;
if (this != &str) {
....
MyString s = "hello";
s = s;
MyString s1("hello");
MyString s2("othello");
MyString s3("byebye");
s1 = s2 = s3;
s1 = ( s2 = s3 );
33
MyString& MyString::operator=( const MyString& str ) {
// see previous implementation
}
MyString& MyString::operator=( MyString& str ) { // no const
// same as before
} // (A)
MyString s1( "hello" );
MyString s2( "mellow" );
s2 = s1; // (B)
MyString s1( "hello" );
MyString s2( "mellow" );
MyString s3( "jello" );
s3 = s1 + s2; // (C)
34
OVERLOADING OF THE [] OPERATOR:
MyString str("hello");
bool MyString::check( int i ) const { // (A)
return ( i >= 0 && i < length ) ? true: false; // (B)
}
char MyString::operator[]( int i ) const { // (C)
if (check(i)) // (D)
return charArr[ i ]; // (E)
else throw Err(); // (F)
}
35
THE WRITE OPERATOR:
void MyString::write( int k, char ch ) {
if (check(k))
charArr[k] = ch;
else throw Err();
}
36
THE + OPERATOR:
MyString s1("hail");
MyString s2("purdue");
MyString s3("yes");
s3 = s1 + s2;
MyString MyString::operator+( MyString str )
{
int temp = length + str.length + 1; // (A)
char* ptr = new char[temp]; // (B)
strcpy( ptr, charArr); // (C)
strcat(ptr, str.charArr); // (D)
MyString s = MyString( ptr ); // (E)
delete[] ptr; // (F)
return s; // (G)
}
37
THE += OPERATOR:
MyString s1("hello");
MyString s2("kitty");
s1 += s2; // hellokitty
MyString MyString::operator+=( MyString str )
{
*this = *this + str; // (A)
return *this;
}
MyString MyString::operator+=( MyString str )
{
return *this + str; // WRONG
}
38
THE == and != OPERATORS:
bool MyString::operator==( MyString str )
{
return strcmp( charArr, str.charArr ) == 0;
}
bool MyString::operator!=( MyString str )
{
return !( *this == str );
}
39
RELATIONAL OPERATORS:
bool MyString::operator>( MyString str )
{
return strcmp( charArr, str.charArr ) > 0;
}
bool MyString::operator<( const MyString str )
{
return strcmp( charArr, str.charArr ) < 0;
}
bool MyString::operator<=( const MyString str )
{
return strcmp( charArr, str.charArr ) <= 0;
}
bool MyString::operator>=( const MyString str )
{
return strcmp( charArr, str.charArr ) >= 0;
}
40
ostream& operator<< ( ostream& os, const MyString& str )
{
os << str.charArr;
return os;
}
istream& operator>> ( istream& is, MyString& str )
{
char* ptr = new char[100];
is >> ptr;
str = MyString( ptr );
return is;
}
41
friend ostream& operator<< ( ostream&, const MyString& );
friend istream& operator>> ( istream&, MyString& );
42
//MyString.cc
#include <cstring> // for C string library
#include <vector> // for iterators, vectors
#include <iostream>
using namespace std;
class MyString;
typedef vector<MyString>::iterator Iter;
int split( Iter, int low, int high );
void quicksort( Iter, int low, int high );
class MyString {
char* charArr;
int length;
class Err {};
public:
MyString() {charArr = 0; length = 0;}
MyString( char* ch )
{
length = strlen( ch );
charArr = new char[ length + 1];
strcpy( charArr, ch );
}
MyString( char ch )
{
length = 1;
charArr = new char[2];
43
*charArr = ch;
*(charArr + 1) = ’\0’;
}
~MyString() { delete[] charArr; }
MyString( const MyString& str )
{
length=str.length;
charArr = new char[length+1];
strcpy( charArr, str.charArr );
}
MyString& operator=( const MyString& str )
{
if (str.charArr == 0) {
delete[] charArr;
charArr = 0;
length = 0;
return *this;
}
if (this != &str) {
delete[] charArr;
charArr = new char[str.length + 1];
strcpy(charArr, str.charArr );
length = str.length;
}
return *this;
}
bool check( int i ) const
{
44
return (i>=0&&i<length)?true:false;
}
char operator[]( int i ) const
{
if (check(i))
return charArr[ i ];
else throw Err();
}
void write( int k, char ch )
{
if (check(k))
charArr[k] = ch;
else throw Err();
}
MyString operator+( const MyString str )
{
int temp = length + str.length + 1;
char* ptr = new char[temp];
strcpy( ptr, charArr);
strcat(ptr, str.charArr);
MyString s = MyString( ptr );
delete[] ptr;
return s;
}
MyString operator+=( const MyString str )
{
*this = *this + str;
return *this;
45
}
MyString operator+=( const char ch )
{
*this = *this + MyString( ch );
return *this;
}
bool operator==( const MyString str )
{
return strcmp( charArr, str.charArr ) == 0;
}
bool operator!=( const MyString str )
{
return !( *this == str );
}
bool operator>( const MyString str )
{
return strcmp( charArr, str.charArr ) > 0;
}
bool operator<( const MyString str )
{
return strcmp( charArr, str.charArr ) < 0;
}
bool operator<=( const MyString str )
{
46
return strcmp( charArr, str.charArr ) <= 0;
}
bool operator>=( const MyString str )
{
return strcmp( charArr, str.charArr ) >= 0;
}
int getSize() const { return length; }
int size() const { return length; }
char* getCharArray() const { return charArr; }
char* c_str() { return charArr; }
int find( char* substring )
{
char* p = strstr( charArr, substring );
return p - charArr;
}
int compare(MyString& str)
{
int z;
char* x = getCharArray();
char* y = str.getCharArray();
if( x == 0 && y == 0 )
47
z = 0;
else if( x != 0 && y == 0 )
z = 1;
else if(x == 0 && y != 0 )
z = -1;
else
z = strcmp( x, y );
return (z);
}
friend ostream& operator<< ( ostream&, const MyString& );
friend istream& operator>> ( istream&, MyString& );
};
ostream& operator<< ( ostream& os, const MyString& str )
{
os << str.charArr;
return os;
}
istream& operator>> ( istream& is, MyString& str )
{
char* ptr = new char[100];
is >> ptr;
str = MyString( ptr );
return is;
}
void sort( Iter first, Iter last )
{
quicksort( first, 0, last - first -1 );
48
}
void quicksort( Iter first, int low, int high )
{
int middle;
if ( low >= high ) return;
middle = split( first, low, high );
quicksort( first, low, middle - 1 );
quicksort( first, middle + 1, high );
}
int split( Iter first, int low, int high )
{
MyString partition_str = *( first + low );
for(;;) {
while ( low < high && partition_str <= *( first + high ) )
high--;
if ( low >= high ) break;
*( first + low++ ) = *( first + high );
while ( low < high && *( first + low ) <= partition_str )
low++;
if ( low >= high ) break;
*( first + high-- ) = *( first + low );
}
*(first + high ) = partition_str;
return high;
}
int main()
49
{
MyString str[] = { "jello", "green", "jolly", "trolley", "abba" };
int size = sizeof(str)/sizeof(str[0]);
vector<MyString> vec(str, &str[size]);
cout << "Initial list: ";
for (Iter p = vec.begin(); p != vec.end(); p++ )
cout << *p << " ";
cout << endl;
sort( vec.begin(), vec.end() );
cout << "Sorted list: ";
for ( Iter p = vec.begin(); p != vec.end(); p++ )
cout << *p << " ";
cout << endl << endl;
MyString s0;
MyString s1 = "hello";
cout << s1.getSize() << endl; // 5
cout << s1 << endl; // hello
MyString s2 = s1;
cout << s2.getSize() << endl; // 5
cout << s2 << endl; // hello
s1.write(0, ’j’);
cout << s1 << endl; // jello
MyString s3 = s1 + s2;
cout << s3 << endl; // jellohello
s3 += s3;
cout << s3 << endl; // jellohellojellohello
MyString s4 = "jello";
if (s1 == s4)
cout << "the operator == works" << endl;
if (s3 > s1)
cout << "the operator > works" << endl;
MyString s5 = "yellow";
50
s1 = s2 = s5;
cout << s1 << endl; // yellow
cout << s2 << endl; // yellow
MyString s6;
s1 = s6;
cout << s1 << endl; // null
}
51
vector<MyString> vec;
sort( vec.begin(), vec.end() );
52
Overloading of Dereferencing Operators
Given a class X
class X {
// ...
};
we can construct an object of this class by invoking its constructor via the
new operator
X* p = new X( ... );
We can dereference the pointer by calling *p, which retrieves the object
for us, or we can dereference the pointer by p->some_member, which first
retrieves the object and then gives us access to the member some member
of class X.
53
From the standpoint of memory management, there is a major shortcom-
ing to using a pointer as a handle to a new object.
When the pointer goes out of scope, only the memory assigned to the
pointer variable — usually four bytes — is freed up.
The memory occupied by the object to which the pointer is pointing is
not freed up unless you invoke the operator delete on the pointer before
it goes out of scope.
54
What this means is that you just have to remember to invoke delete
somewhere for every new (and delete[] somewhere for every new[]).
In a majority of situations, this programming rule of thumb works just
fine to prevent memory leaks.
But there can be special circumstances where this rule of thumb cannot
be applied so easily.
55
//PretendGiant.cc
class Giant{};
class Big {};
class MyClass {
Giant* giant;
Big* big;
public:
MyClass()
: giant( new Giant() ), //(A)
big( new Big() ) //(B)
{}
~MyClass() { delete giant; delete big; }
};
int main()
{
MyClass myobject; //(C)
return 0;
}
Let’s pretend that objects of type Giant and Big occupy huge amounts of memoryand that after the execution of the statement in line (A), there is not enoughmemory left over for the construction in line (B).
This could cause an exception to be thrown, which would halt any further con-struction of the object in line (C).
56
//ConstructorLeak.cc
class Err{};
class Giant{};
class Big {
public:
Big() throw( Err ) { throw Err(); } //(D)
};
class MyClass {
Giant* giant;
Big* big;
public:
MyClass() : giant( new Giant() ), big( new Big() ) {} //(E)
~MyClass() { delete giant; delete big; }
};
int main()
{
try {
MyClass myobject; //(F)
} catch( Err ) {}
return 0;
}
57
One way to eliminate such potential memory leaks is by using a smart
pointer instead of a regular pointer. When a smart pointer goes out
of scope, it takes care of cleaning up after itself — in the sense that
its destructor is invoked to free up the memory occupied by the object
to which the pointer points.
58
class SmartPtr {
X* ptr;
public:
//constructor
SmartPtr( X* p ) : ptr( p ) {};
// overloading of *
X& operator*() { return *ptr; }
// overloading of ->
X* operator->() { return ptr; }
//....
};
An object of type SmartPtr is simply a wrapper around an object of type
X*.
59
X* s = new X( .... );
SmartPtr smart_p( s );
60
Due to the overloading defined in the SmartPtr class for the member
access operator ‘->,’ we may now access a member of the X object con-
structed by
smart_p->some_member;
61
//SmartPtrInitial.cc
#include <iostream>
using namespace std;
class X {};
class SmartPtr {
X* ptr;
public:
SmartPtr( X* p ) : ptr( p ) {};
X& operator*() { return *ptr; }
X* operator->() { return ptr; }
~SmartPtr() {
delete ptr;
cout << "Memory pointed to by ptr freed up" << endl;
}
};
int main()
{
X* xp = new X();
SmartPtr s( xp );
return 0;
}
62
//SmartPtrWithOwnership.cc
class X {};
class SmartPtr {
X* ptr;
public:
explicit SmartPtr( X* p = 0 ) : ptr( p ) {};
X& operator*() { return *ptr; }
X* operator->() { return ptr; }
SmartPtr( SmartPtr& other ) : ptr( other.release() ) {}
SmartPtr operator=( SmartPtr& other ) {
if ( this != &other )
reset( other.release() );
return *this;
}
~SmartPtr() { delete ptr; }
X* release() {
X* oldPtr = ptr;
ptr = 0;
return oldPtr;
}
void reset( X* newPtr ) {
if ( ptr != newPtr ) {
delete ptr;
63
ptr = newPtr;
}
}
}; // end of SmartPtr class
int main()
{
X* xp = new X();
SmartPtr s1( xp );
SmartPtr s2 = s1; // test copy const (s2 now owns X object)
SmartPtr s3; // use no-arg constructor
s3 = s2; // test copy assign (s3 now owns X object)
return 0;
}
64
//SmartPtr.h
template<class T> class SmartPtr {
T* ptr;
public:
explicit SmartPtr( T* p = 0 ) : ptr( p ) {}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
SmartPtr( SmartPtr<T>& other ) : ptr( other.release() ) {}
SmartPtr operator=( SmartPtr<T>& other ) {
if ( this != &other )
reset( other.release() );
return *this;
}
~SmartPtr() { delete ptr; }
T* release() {
T* oldPtr = ptr;
ptr = 0;
return oldPtr;
}
void reset( T* newPtr ) {
if ( ptr != newPtr ) {
delete ptr;
ptr = newPtr;
}
65
}
};
66
//ConstructorLeakPlugged.cc
#include "SmartPtr.h"
#include <iostream>
using namespace std;
class Err{};
class Giant {
public:
~Giant() {cout << "Giant’s destructor invoked" << endl;}
};
class Big {
public:
Big() throw( Err ) { throw Err(); }
~Big() {cout << "Big’s destructor invoked" << endl;}
};
class MyClass {
SmartPtr<Giant> giant;
SmartPtr<Big> big;
public:
MyClass() : giant( 0 ), big( 0 ) {
giant.reset( new Giant() );
big.reset( new Big() );
}
~MyClass() {} // no destructor needed anymore
67
};
int main()
{
try {
MyClass myclass;
} catch( Err ) {}
return 0;
}
68