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