0% found this document useful (0 votes)
11 views

COMP2006 Lecture 12 Casting and Operator Overloading

This lecture discusses casting in C++ including static cast, dynamic cast, const cast, and reinterpret cast as well as operator overloading. It provides examples of using each type of cast and explains when each should be used. The document also discusses overloading operators like + and >> and provides an example of a custom class called MyFloat that overloads operators for printing.

Uploaded by

zombiten3
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views

COMP2006 Lecture 12 Casting and Operator Overloading

This lecture discusses casting in C++ including static cast, dynamic cast, const cast, and reinterpret cast as well as operator overloading. It provides examples of using each type of cast and explains when each should be used. The document also discusses overloading operators like + and >> and provides an example of a custom class called MyFloat that overloads operators for printing.

Uploaded by

zombiten3
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 45

COMP2006

C++ Programming
Lecture 12

Dr Chao Chen

1
This Lecture
• Casting
– static cast
– dynamic cast
– const cast
– reinterpret cast
• Operator overloading
• Questions

2
Breaking the rules

Casting

3
Unchangable values?
• Here we have constant references passed in
• Q1: Can we use x and y to change the variable values?

void foo(
const int& x,
const int& y )
{
x = 5;
y = 19;
}

• Q2: Can we add anything to allow us to be able to


change them?
4
Casting away the const-ness
• Remove the constness of a reference or pointer
void foo( const int& x, const int& y )
{
int& xr = (int&)(x);
// Since we cast away const-ness we CAN do this
xr = 5; WARNING!
// or this Do not actually do this
int& yr = (int&)(y); unless there is a REALLY
yr = 19; good reason!
}
Casting away
int main() const-ness is
{ usually very bad
int x = 4, y = 2; We would like some
foo( x, y ); way to find all of
cout << "x = " << x << ", y = “ the places in our
<< y << endl; code where we were
5
} bad – bent the rules
const_cast <type> (var)
• Remove the constness of a reference or pointer
void foo( const int& x, const int& y )
{
int& xr = const_cast<int&>(x);
// Since we cast away const-ness we CAN do this
xr = 5;
// or this
int& yr = const_cast<int&>(y); WARNING AGAIN
yr = 19; Do not actually do this
} unless there is a REALLY
good reason!
int main() Casting away const-ness
{ is usually very bad
int x = 4, y = 2;
foo( x, y );
cout << "x = " << x << ", y = “
<< y << endl;
6
}
Four new casts
• const_cast<newtype>(?)
– Get rid of (or add) ‘const’ness (or volatile-ness)
• dynamic_cast<newtype>(?)
– Safely cast a pointer or reference from base-class to
sub-class
– Checks that it really IS a sub-class object
• static_cast<newtype>(?)
– Cast between types, converting the type
• reinterpret_cast<newtype>(?)
– Interpret the bits in one type as another
– Mainly needed for low-level code
– Effects are often platform-dependent
– i.e. ‘treat the thing at this address as if it was a…’
7
Why use the new casts?
• This syntax makes the presence of casts more
obvious
– Casts mean you are ‘bending the rules’ somehow
– It is useful to be able to find all places that you do this
• This syntax makes the purpose of the cast more
obvious
– i.e. casting to remove ‘const’ or to change the type
• Four types give more control over what you
mean, and help you to identify the effects
• Sometimes needed: dynamic_cast provides
run-time type checking
• Note: Casting a pointer will not usually change
the stored address value, only the type. This is
NOT true with multiple inheritance
8
static_cast <type> (var)
• static_cast<newtype>(oldvariable)
– Commonly used cast
– Attempts to convert correctly between two types
– Usually use this when not removing const-ness and
there is no need to check the sub-class type at runtime

void static_cast_example()
{
float f = 4.1;
// Convert float to an int
int i = static_cast<int>(f);
printf( "f = %f, i = %d\n", f, i );
} 9
dynamic_cast <type> (var)
• Casting from derived class to base class is easy
– Derived class object IS a base class object
– Base class pointer might not point at a derived class object
• dynamic_cast<>()
– Safely convert from a base-class pointer or reference to a sub-
class pointer or reference
– Checks the type at run-time rather than compile-time
– Returns NULL if the type conversion of a pointer cannot take
place (i.e. it is not of the target type)
– There is no such thing as a NULL reference
If reference conversion fails, it throws an exception of type
std::bad_cast
• Note: We will come to exceptions later – think of them as
similar to the Java exceptions for the moment
10
static_cast example
sub1 s1;
base
sub1* ps1 = &s1;

sub1 sub2
// Fine: treat as base class
base* pb1 = ps1;

// Treat as sub-class
sub2* ps2err = static_cast<sub2*>(pb1);
// Static cast: do conversion.
ps2err->func();
// This is a BAD practice.
// Treating sub1 object as a sub2 object
11
dynamic_cast example
sub1 s1;
sub1* ps1 = &s1; base

// Fine: treat as base class sub1 sub2


base* pb1 = ps1;

// Treat as sub-class
sub2* ps2safe = dynamic_cast<sub2*>(pb1);
// Dynamic cast: runtime check
if ( ps2safe == NULL )
printf( "Dynamic cast on pb2 failed\n" );
else
ps2safe->func();
12
Exception thrown by dynamic_cast
void foo()
{ Dynamic cast on a reference
Sub1 s1;
Base& rb = s1;
Sub2& rs2 = dynamic_cast<Sub2&>(rb);
cout << "No exception was thrown by foo()" << endl;
}
int main() class Base
{
try
{
foo(); class Sub1 class Sub2
}
catch ( bad_cast )
{ cout << "bad_cast exception thrown" << endl; }
catch ( ... )
{ cout << "Other exception thrown" << endl; }
} 13
Note: s1 is destroyed properly when stack frame is destroyed
reinterpret_cast<type>(var)
• reinterpret_cast<>()
– Treat the value as if it was a different type
– Interpret the bits in one type as another
– Including platform dependent conversions
– Hardly ever needed, apart from with low-level code
– Like saying “Trust me, you can treat it as one of these”
– e.g.:
void reinterpret_cast_example()
{
int i = 1;
int* p = & i;
i = reinterpret_cast<long>(p);
printf( "i = %x, p = %p\n", i, p );
} 14
A Casting Question
• Where are casts needed, and what sort of
casts should be used?
(Assume BouncingBall is a sub-class of BaseEngine)

BouncingBall game;
BaseEngine* pGame = &game; // ?
BouncingBall* pmGame = pGame; // ?

BouncingBall game;
BaseEngine& rgame = game; // ?
BouncingBall& rmgame = rgame; // ?
15
Answer : pointers
BouncingBall game;
BaseEngine* pGame = &game; // No cast
BouncingBall* pmGame =
dynamic_cast<BouncingBall*>(pGame);
if ( pGame==NULL ) { /* Failed */ }

No cast needed to go from sub-class to base class.

In this case, because the game object really is a


BouncingBall, a static_cast would have worked.
But would not have checked this – would have been BAD!
16
Answer : references
BouncingBall game;
BaseGameEngine& rgame = game; // No cast
try
{
BouncingBall& rmgame =
dynamic_cast<BouncingBall&>(rgame);
}
catch ( std::bad_cast b )
{
// Handle the exception
// Happens if rgame is NOT a BouncingBall
}

Need to check for any exceptions being thrown for references

Again, in this case, because the rgame really is a BouncingBall, a


static_cast would have worked. But would have been BAD!
17
Operator overloading

e.g. why does adding


together two strings
concatenate them?

18
Examples
#include <string>
#include <iostream>

using namespace std;

int main()
{
string s1( "Test string" );
int i = 1; Overloaded operator - input

cin >> i; Overloaded operator - output

cout << s1 << " " << i << endl;

cerr << s1.c_str() << endl;


}
19
Operator overloading
• Function overloading:
– Change the meaning of a function according
to the types of the parameters
• Operator overloading
– Change the meaning of an operator according
to the types of the parameters
• Change what an operator means?
– Danger! Could make it harder to understand!
• Useful sometimes, do not overuse it
– e.g. + to concatenate two strings
20
My new class: MyFloat
#include <iostream>
using namespace std;
Wraps up a float
And a string describing it
class MyFloat
{
public:
// Constructors
MyFloat( const char* szName, float f )
char* and float
: f(f), strName(szName) {}

MyFloat( string strName, float f )


string and float
: f(f), strName(strName) {}

private:
float f;
Internal string and float
string strName;
}; 21
Printing – member functions
// Constructor
MyFloat( const char* szName, float f )
: f(f), strName(szName)
{}

// Print details of MyFloat


void print()
{
cout << strName << " : " << f << endl;
}

Main function:
MyFloat f1("f1", 1.1f);
f1 : 1.1
f1.print();
f2 : 3.3
MyFloat f2("f2", 3.3f);
f2.print();
22
Non-member operator overload
MyFloat operator-( const MyFloat& lhs, const MyFloat& rhs )
{
MyFloat temp(
lhs.strName + "-" + rhs.strName, /* strName */
lhs.f - rhs.f); /* f, float value */
return temp;
}

class MyFloat
{

// Non-member operator overload – friend can access private
friend MyFloat operator-( const MyFloat& lhs,
const MyFloat& rhs );

23
}
Non-member operator overload
MyFloat operator-( const MyFloat& lhs, const MyFloat& rhs )
{
MyFloat temp( Calls the two-parameter constructor
lhs.strName + "-" + rhs.strName, /* strName */
lhs.f - rhs.f); /* f, float value */
return temp;
Uses + on the strings
}

class MyFloat
Needs to be a friend in this case, to
{
access the private parts (no ‘getters’)

// Non-member operator overload – friend can access private
friend MyFloat operator-( const MyFloat& lhs,
const MyFloat& rhs );

24
}
Non-member operator overload
MyFloat operator-( const MyFloat& lhs, const MyFloat& rhs )
{
MyFloat temp(
lhs.strName + "-" + rhs.strName, /* strName */
lhs.f - rhs.f); /* f, float value */
return temp;
}

MyFloat f1("f1", 1.1f);


f1 : 1.1
MyFloat f2("f2", 3.3f); f2 : 3.3

MyFloat f3 = f1 - f2;
Output: f1-f2 : -2.2
f3.print();

25
Or simplified version…
MyFloat operator-( const MyFloat& lhs, const MyFloat& rhs )
{
return MyFloat( lhs.strName + "-" + rhs.strName,
lhs.f - rhs.f );
}

MyFloat f1("f1", 1.1f);


f1 : 1.1
MyFloat f2("f2", 3.3f); f2 : 3.3

MyFloat f3 = f1 - f2;
Output: f1-f2 : -2.2
f3.print();

26
Member function version
MyFloat MyFloat::operator + ( const MyFloat& rhs ) const
{
return MyFloat( this->strName + "+" + rhs.strName,
this->f + rhs.f );
}

MyFloat f1("f1", 1.1f);


class MyFloat MyFloat f2("f2", 3.3f);
{ MyFloat f4 = f1 + f2;
public: f4.print();
// Member operator
MyFloat operator+ ( Output:
const MyFloat& rhs ) f1+f2 : 4.4
const;
};
27
Member vs non-member versions
// Member function:
MyFloat MyFloat::operator+ (const MyFloat& rhs) const

// Non-member function
MyFloat operator- ( const MyFloat& lhs,
const MyFloat& rhs )

Member function: Global function:


(‘this’ is lhs) Explicitly state lhs
MyFloat Return type MyFloat
MyFloat:: operator- (
LHS const
operator+ (
MyFloat& lhs,
const MyFloat& rhs const MyFloat& rhs
RHS
) )
const 28
Differences ?
// Member function:
MyFloat MyFloat::operator+ (const MyFloat& rhs) const

// Non-member function
friend MyFloat operator- (const MyFloat& lhs,
const MyFloat& rhs )

// These would work:


MyFloat f5 = f1.operator+( f2 ); f5.print();
MyFloat f6 = operator-( f1, f2 ); f6.print();

// These would not compile:


MyFloat f7 = operator+( f1, f2 ); f7.print();
MyFloat f8 = f1.operator-( f2 ); f8.print();
29
Operator overloading restrictions
• You cannot change an operator's precedence
– i.e. the order of processing operators
• You cannot create new operators
– Can only use the existing operators
• You cannot provide default parameter values
• You cannot change number of parameters (operands)
• You cannot override some operators:
:: sizeof ?: or . (dot)
• You must overload +, += etc separately
– Overloading one does not overload the others
• Some can only be overloaded as member functions:
= , [] and ->
• Postfix and prefix ++ and –- are different
– Postfix has an unused int parameter
30
Post vs Pre-increment
Assignment vs comparison
And + vs +=

31
Key tricky concepts
1. = is not the same as ==
– Assignment operator = is created by default
– Equivalence operation == is NOT
2. How do we supply implementations for
both ob++ and ++ob?
3. != and == are different
– Although you could return ! (x==y) for the !=
4. + and += are different
– Similarly for other similar operators
32
== vs = operators
class C
• The code on the left will NOT
{ compile:
public:
C( int v1=1, int v2=2 ) g++ file.cpp
: i1(v1), i2(v2)
{} In function `int main()':
file.cpp:17: error: no
int i1, i2; match for 'operator=='
}; in 'c1 == c2'

int main() • i.e. there is no == operator


{ defined by default
C c1, c2; • Pointers could be compared
though, but not the objects
if ( c1 == c2 ) themselves
{ • NB: Assignment operator IS
printf( "Match" ); defined by default (it is one of
} the four functions created by
} compiler when necessary) 33
Post-increment vs pre-increment
MyFloat MyFloat::operator ++ ( int ) MyFloat f9 = f5++;
{
MyFloat temp( // Make a copy cout << "Orig: ";
string("(") + strName +")++", f ); f5.print();
// NOW increment original cout << "New : ";
f++; f9.print();
return temp; // Return the copy
}
Note: (i++)++ No!

MyFloat& MyFloat::operator ++ () MyFloat f10 = ++f6;


{
++f; // Increment f first cout << "Orig: ";
strName = f6.print();
string("++(") + strName +")"; cout << "New : ";
return *this; // Return object itself f10.print();
}
Note: ++(++i) Yes 34
+ and += are different
const means member
MyClass MyClass::operator+ function does not alter
(const MyClass &other) const the object
{ i.e. makes the this
pointer constant
MyClass temp;
// set temp.… to be this->… + other.…
return temp; // copy MyClass m1,m2,m3,m4;
} m1 = m2 + m3 + m4;
MyClass& MyClass::operator+=
(const MyClass &other)
{
// set this->… to this->… + other.…
return *this;
} MyClass m1, m2, m3;
(m1 += m2) += m3; 35
!= can be defined using ==
bool MyClass::operator==
(const MyClass &other) const
{
// Compare values
// Return true or false const means member
function does not alter
} the object

bool MyClass::operator!=
(const MyClass &other) const
{
return !(*this == other);
}
36
Operator overloading summary
• Can define/change meaning of an operator, e.g.:
MyFlt operator-(const MyFlt&, const MyFlt&);
• You can make the functions member functions
MyFlt MyFlt::operator-(const MyFlt& rhs) const;
– Left hand side is then the object it is acting upon
• Act like any other function, only syntax is different:
– Converts a-b to a.operator-(b) or operator-(a,b)
• Access rights like any other function
– e.g. has to be a friend or member to access private/protected
member data/functions

• Also, parameter types can differ from each other, e.g.


MyFlt operator-( const MyFlt&, int );
– Would allow an int to be subtracted from a MyFlt
37
Streams use operator
overloading

38
Earlier example, again
#include <string> Cin, cout and cerr are objects
#include <iostream> extern istream cin;
extern ostream cout;
using namespace std; extern ostream cerr;

int main()
{ >> is implemented for the istream
string s1( "Test string" ); class for each type of value on the
int i = 1; left-hand side of the operator

i.e. different function


cin >> i; called depending
upon object type
cout << s1 << " " << i << endl; so it can change
its behaviour
cerr << s1.c_str() << endl;
}
The stream object is returned,
to allow chaining together 39
Example of how ostream works
template<class _Traits> inline
basic_ostream<char, _Traits>& operator<<
( basic_ostream<char, _Traits>& _Ostr, const char *_Val)
{…
ostream& operator<< ( ostream&, const char* )

• Template class – we’ll see this idea next (Java generics)


• The stream class basic_ostream<> isn’t obvious to us
• Key point is that they have the format of this type…
stream& operator<<( stream& stream, const char *_Val )
stream& operator<<( stream& stream, int _Val )
stream& operator<<( stream& stream, double _Val )
• Correct operator overload is called for parameter type
• Returns a reference to the parameter stream
40
Common operators
• Examples: https://en.cppreference.com/w/cpp/language/operators
T& operator=(const T& other) …
X& operator+=(const X& rhs)
X& operator++() … vs X operator++(int) …
inline bool operator< (const X& lhs, const X& rhs)
{ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs)
{ return rhs < lhs; }
inline bool operator<=(const X& lhs, const X& rhs)
{ return !(lhs > rhs); }
inline bool operator>=(const X& lhs, const X& rhs)
{ return !(lhs < rhs); }
std::ostream& operator<<(std::ostream& os, const T& obj)
std::istream& operator>>(std::istream& is, T& obj) 41
Summary

42
Operator overloading - what to know
• Know that you can change the meaning of
operators
• Know that operator overloading is available
as both a member function version and a
global (non-member) function version
• Be able to provide the code for the
overloading of an operator
– Parameter types, const?
– Return type
– Return value? (*this) or a new stack object?
– Simple implementations
43
Questions to ask yourself
• Define as a member or as a global?
– If global then does it need to be a friend?
• What should the parameter types be?
– References?
– Make them const if you can
– If member, should the function be const?
• What should the return type be?
– Should it return *this and return reference?
– Does it need to return a copy of the object?
• e.g. post-increment must return a copy
44
Next lecture

• Template functions
– And how to fully replace macros
– Using inline functions too
– Think ‘Java Generics’, <>
• Template classes
– How standard template library works

45

You might also like