• No results found

Operator Overloading. Lecture 8. Operator Overloading. Running Example: Complex Numbers. Syntax. What can be overloaded. Syntax -- First Example

N/A
N/A
Protected

Academic year: 2021

Share "Operator Overloading. Lecture 8. Operator Overloading. Running Example: Complex Numbers. Syntax. What can be overloaded. Syntax -- First Example"

Copied!
7
0
0

Loading.... (view fulltext now)

Full text

(1)

CS 600.120 Intermediate Programming

Lecture 8 Operator Overloading

CS 600.120 Intermediate Programming

Operator Overloading

• C++ feature that allows implementer-defined classes to specify class-specific function for operators

• Benefits

– allows classes to provide natural semantics for operators in a manner similar to built-in types

• Drawbacks

– poor use leads to confusing code

– many classes do not have natural overloaded semantics and, thus, overloaded operators are ambiguous

• it is not clear that this situation is worse than with poorly named functions

• however, it was serious enough that Java left the feature out

CS 600.120 Intermediate Programming

Running Example: Complex Numbers

• Complex numbers

– natural class for operator overloading – operators against this class are

• well-defined

• conform with user expectation – thus, they are easy to use

class complex { public:

...

private:

double _real;

double _imag;

};

CS 600.120 Intermediate Programming

Syntax

• Operators are treated like functions

– take arguments, allow implementer to define semantics

• Operators are invoked differently than functions – unary operators (take one argument)

• usage: -a, *b – binary operators

• usage: a + b, c <= d

• Mapping operators to functions – usage: a + b

– matches prototypes

• complex & complex::operator+ ( complex & other )

CS 600.120 Intermediate Programming

Syntax -- First Example

• operator+ -- the first example

• The following are equivalent

– note that the compiler will factor out the temporary variables – I prefer the first actually

• (aside) don’t be afraid to use temporary variables when they simplify code

complex & complex::operator+ ( const complex & other ) const {

double real = this._real + other._real;

double imag = this._imag + other._imag;

return complex ( real, imag );

} // Simplified

complex & complex::operator+ ( const complex & other ) const {

return complex ( _real + other._real, _imag + other._imag );

}

CS 600.120 Intermediate Programming

What can be overloaded

• Almost all operators

• Arithmetic – +, -, *, /, %, ++, --

• Bit-wise operators – ^, &, >>, <<

• Logical operators – &&, ||, !

• Comparison – >, <, <=, >=, ==, !=

• Array, function, comma – [], (), , , ->, ->*

• Assignment and composite operators – =, ^=, |=, &=, +=, -=, *=, /=, %=, <<=, >>=,

(2)

CS 600.120 Intermediate Programming

What cannot be done

• Some operators cannot be overloaded

• Non-pointer access to class members – ., .*

• Scope – ::

• If/then/else operator – ?:

• New operators cannot be created, even if desired – |x|

– y := x – y = x ** 2

CS 600.120 Intermediate Programming

const and

• Almost all overloaded operators (except those involving equality) should be const, i.e. they should not change the implicit argument

– similarly, they should use const function arguments

complex & complex::operator+ ( const complex & other ) const {

return complex ( _real + other._real, _imag + other._imag );

}

complex a ( 1, 2);

complex b ( 2, 3);

complex c = a + b; // Should not change a or b

CS 600.120 Intermediate Programming

Unary and Binary

• Both the unary and binary forms of an operator can be overloaded – differentiated by their arguments

– applies to: +, -, *, &

complex & complex::operator- () const {

return complex ( -1 * _real, -1 * imag);

}

complex & complex::operator- ( const complex & other ) const {

return complex ( _real - other._real, _imag - other._imag );

}

CS 600.120 Intermediate Programming

Anonymous Objects and

• First (well motivated) use of anonymous objects – note it is often wrong (almost always) to modify this – same reason we use const

// Correct

complex & complex::operator- () const {

return complex ( -1 * _real, -1 * _imag);

}

// Changes this

complex & complex::operator- () {

_real *= -1;

_imag *= -1;

return *this;

}

complex a ( 1, 2);

complex b = -a; // The right definition of operator- // leaves a unmodified

Anonymous Objects and

• First (well motivated) use of anonymous objects – note it is often wrong (almost always) to modify this – same reason we use const

// Correct

complex & complex::operator- () const {

return complex ( -1 * _real, -1 * _imag);

}

// Returns reference to stack variable complex & complex::operator- () {

complex c;

c._real = _real * -1;

c._image = _imag * -1;

return c;

}

complex a ( 1, 2);

complex b = -a; // The right definition of operator- // leaves a unmodified

Polymorphic Functions and

• Operator overloading is done through functions, and, thus, may be polymorphic

– actually, unary and binary forms are already polymorphic

complex & complex::operator- ( const double a ) const {

return complex ( _real - a );

}

inumber & inumber::operator- ( inumber & other ) const {

return complex ( _real - other._real, _imag - other._imag );

}

(3)

CS 600.120 Intermediate Programming

Class Arguments and Global Scope

• One argument to a operator overloaded function must be a class argument

– i.e. it is not possible to change the implementation of operators against built-in types

• Operators may also be of global scope – the following are equivalent – note that the second is not a class member

– It is preferable to have overloaded operators belong to classes. Why?

complex & complex::operator+ ( const complex & other ) const;

complex & operator+ ( const complex & other1, const complex & other2 ) const;

CS 600.120 Intermediate Programming

Common Overloaded Operators

• Now let’s construct some common operators for the complex class – operator<< and operator>>

– operator=

– operator<

• Interestingly, all of these common cases have interesting effects that do not conform well to the operator overloading model

CS 600.120 Intermediate Programming

operator>> and operator<<

• So we want to print out our imaginary numbers – What is the prototype of the function?

– Is it binary or unary?

– What are the arguments?

xxx operator<< ( const yyy, const zzz );

xxx operator<< ( const yyy );

complex a ( 1.0, 1.0);

cout << a << endl; // prints “1.0 + 1.0i”

CS 600.120 Intermediate Programming

operator>> and operator<<

• Two possible prototypes – class scope – global scope

• What is the problem with the first prototype?

• Can we make the operator a member of class complex?

ostream & ostream::operator<< ( const complex & other );

ostream & operator<< ( ostream& os, const complex & other );

CS 600.120 Intermediate Programming

Some properties of ostream

• Main ostream operator

– ostream & ostream::operator<< ( const char [] )

• Also provides for all basic types

– ostream & ostream::operator<< ( const type & other );

• Because it returns an ostream reference, the arguments maybe chained – cout << “The value of PI is approximately “ << 3.14159 << endl;

• Note that the function is not constant – changes the state of the ostream object

CS 600.120 Intermediate Programming

operator>> and operator<<

• Implementation of the operator – must choose the operator to be global scope – we cannot modify class ostream

ostream & operator<< ( ostream& os, const complex & other ) {

}

(4)

CS 600.120 Intermediate Programming

operator>> and operator<<

• Implementation of the operator – must choose the operator to be global scope – we cannot modify class ostream – How did we do?

• All arguments for operator<< apply to operator>>

– just for class istream

ostream & operator<< ( ostream& os, const complex & other ) {

os << other._real << “ + “ << other._imag << “i”;

return os;

}

CS 600.120 Intermediate Programming

operator<

• An obvious operator with an (usually) obvious implementation – complex numbers are not well-ordered mathematically speaking

• Different example -- countdown timer class CDT

bool CDT::operator< ( const CDT & other ) const {

return ( _seconds < other.seconds ) }

CS 600.120 Intermediate Programming

operator<

• The catch, when one defines operator<

• The compiler also creates

– operator> ( a < b can be used to construct b > a ) – operator<= ( !( a < b ) implies a <= b ) – operator>= ( !( b < a ) implies a >= b )

• What is the danger here?

CS 600.120 Intermediate Programming

operator<

• The catch, when one defines operator<

• The compiler also creates

– operator> ( use a < b, but switch arguments b > a ) – operator<= ( !( a < b ) implies a <= b )

– operator>= ( !( b < a ) implies a >= b )

• What is the danger here?

– if operator< does not provide a total ordering, then the other operators will not be well defined

– one good example is that of line segments

• operator< resolves the segment based on length

• any two segments of the same length will be declared equal

operator=

• The C++ compiler always provides an overloaded operator=

– even when not requested – provides a byte-wise copy of the object – deep copy of built in types – shallow copy of pointers

• Every class should provide its own operator=

– avoids confusing/unwanted default behavior – frequently a simple function

– often identical to the default

• We will discuss shallow and deep copying tomorrow

complex & operator= ( const complex & other ) {

this._real = other._real;

this._imag = other._imag;

}

Type Conversions

• Constructors are the preferred method of converting the type of an object

– e.g. the following converts a double to a complex – let’s write it

complex::complex ( double real ) {

_real = real;

_imag = 0;

}

(5)

CS 600.120 Intermediate Programming

Type Conversions

• Constructors are the preferred method of converting the type of an object

– e.g. the following converts a double to a complex – how did we do?

complex::complex ( double real ) : _real ( real ), _imag ( 0 ) {

}

CS 600.120 Intermediate Programming

Type Conversions

• Constructors are the preferred method of converting the type of an object

– Why are they the preferred technique?

– What is the alternative?

• Any constructor that accepts a single argument doubles as a type conversion routine

CS 600.120 Intermediate Programming

Type Conversion with Operators

• Two circumstances under which we cannot perform type conversion via constructors

– they are?

CS 600.120 Intermediate Programming

Type Conversion with Operators

• Two circumstances under which we cannot perform type conversion via constructors

– when the target type is not a class

– when the target type is a class that the implementer cannot modify

CS 600.120 Intermediate Programming

Type Conversion with Operators

• Two circumstances under which we cannot perform type conversion via constructors

– when the target type is not a class

– when the target type is a class that the implementer cannot modify

• Type conversion can be performed with operators

• For example, if we want to construct a string out of complex

complex::operator string () const {

char buffer [21];

sprintf ( buffer, “%d + %di”, _real, _imag );

return string ( buffer );

}

CS 600.120 Intermediate Programming

Type Conversion with Operators

• Some observations

– we cannot do this by providing a new constructor in string

• this would be preferred, but we cannot change string – operator string() does not have strict function semantics

• does not specify a return type

complex::operator string() const {

char buffer [21];

sprintf ( buffer, “%d + %di”, _real, _imag );

return string ( buffer );

}

(6)

CS 600.120 Intermediate Programming

operator[]

• Frequently overloaded in container classes

– C++ standard class vector overloads to provide array-like behavior

• Simple example -- look at the contents of a stack of items

• Rarely a good idea to overload

– unless the class is essentially a semantically-rich array class stack

{ public:

item & operator[] ( int which ) const;

private:

vector <item> v;

};

item & operator[] ( int which ) const {

if (( which < v.length() ) && ( which >= 0 )) {

return v[which];

} }

CS 600.120 Intermediate Programming

Questions

• Why can one overload the operators that access class members through pointers (->, ->*), while it is illegal to overload standard class access members (.,.*)?

CS 600.120 Intermediate Programming

Why/When/What to not Overload

• confusing precedence

• expected equivalence

CS 600.120 Intermediate Programming

Precedence

• Benefit of operators is operator-like syntax, rather than functional syntax

– convenient, intuitive and concise, .... right?

– but, why are there RPN calculators?

• Operator evaluation relies on evaluation ordering rules – C++ is not the same as mathematics

– for example, ^ is the C++ bitwise exclusive-or (not exponentiation) – overloading for exponentiation does not work

• a ^ 2 + 1 evaluates to a.operator^(2+1) – math would prefer a.operator^(2) + 1

• Functions do not have precedence problems – functional notation (parentheses) ensure this

Equivalence Operations

• We have an expectation that the following are the same:

– a = a + 1;

– a++;

– ++a;

– a += 1;

• However, they invoke four entirely different functions

• It is the responsibility of the implementer to provide a complete and logically consistent set of operators

– frequently a tedious (and often incomplete in my experience) task

Why no operator overloading in Java

• Book gives a lot of guidelines on operator overloading – merits and demerits

– dangers of usage

• Why would Java leave this (well-established) feature out of a very similar OO language?

• Do you agree with the decision?

• What key C++ feature relies on operator overloading?

– interestingly, Java is weak in this department

(7)

CS 600.120 Intermediate Programming

How bad does it get?

• Frequently, for error testing, it is desirable to overload – class::operator bool( )

• Used to detect errors – ostream::operator bool( )

– returns true if an error on the stream, false otherwise

cout << x << y << z << endl;

if ( cout ) {

// process error }

CS 600.120 Intermediate Programming

How bad does it get?

• However, for compatibility reasons, bool can be coerced (by the compiler) automatically to int.

– consider the code snippet below

– it would compile and attempt to shift cout x bits to the right

cout >> x; // probably meant cout << x if ( cout )

{

// process error }

CS 600.120 Intermediate Programming

How do we solve this?

• Overloading class::operator void* () solves the problem – void* cannot be shifted or manipulated by other operators – stupid idiom to address a stupid problem

cout >> x; // this now hurls an error as cout cannot // be coerced to a integer if ( cout )

{

// process error }

CS 600.120 Intermediate Programming

Beware of Compiler Coercion

• If a compiler cannot find an exact match for a function prototype that you have provided, it will find a near match by coercion

– our example, converting bool to int

• This is dangerous because often it is unintended

– running the compiler with -Wall will let the implementer know when all such implicit coercions occur

– this is a very good idea

int x = 1;

int y = -7;

complex a ( x, y ); // invokes complex ( double, double ) // not so dangerous, safe-ish coercion double x = 54.2;

CoundownTimer cdt ( x ); // invokes CountdownTimer ( int ) // losing the .2 seconds

References

Related documents

BỒI DƯỠNG TOÁN - LÍ - HÓA CẤP 2+3 1000B TRẦN HƯNG ĐẠO TP.QUY

Using T1-weighted MRI scans acquired from 1616 individuals with OCD and 1463 healthy controls across 37 datasets participating in the ENIGMA- OCD Working Group, we

This study aimed to audit AAA surgery and evaluate the application of the Portsmouth Physiological and Operative Severity Score for the Enumeration of Mortality and Morbidity

// Notice that no changes are required for the + operator // with regard to the initial implementation of the class Point Point::operator + (const Point&amp; p) const {}.

– protected : Derived classes and friend s can access protected members of the base

1 example_wrap.c generated C code, 2 example_wrap.o compiled object file, 3 _example.so shared object file, and 4 example.py Python code for wrapper... Summary and

• Be careful when implementing the assignment operator especially if you have data member which are pointers and you delete, allocate. memory for this pointers in your