Operator Overloading in C++ Questionspammann/332/ppt/kak/slides12b.pdf · Operator Overloading in...

Post on 02-Jun-2020

28 views 0 download

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