CIS-255 Home http://www.c-jump.com/bcc/c255c/c255syllabus.htm

1. C++ Operators

• A simple mechanism for improving program readability...

...or for making programs completely unreadable!

• Syntactic sugar replacing function calls.

• The idea is to allow operator syntax for objects:

```
Point p1;
Point p2;

//...
if ( p1 == p2 ) {
//...
}

```

2. Kinds of operators

• Numerical: +, -, *, ++, %, etc.

• Comparison: >, >=, ==, !=, etc.

• Structural: [], *, &, etc.

• One operator is more important than the others:

• The assignment operator is provided (at no cost) if you don't provide one yourself...

...it might not be what you want.

• Default assignment does assignment of all member objects, and bitwise copy of all native types.

• Objects containing addresses (pointers) will be assigned shallowly.

3. Arguments of operators

• Operators are functions.

• Operators are (almost) always unary or binary.

• Operators can be members, or regular functions.

• How many arguments does an operator take?

•
Unary Binary
Member 0 1
Non-member 1 2
• It's an error to declare an operator with the wrong number of arguments.

4. Rational numbers

• A rational number is a number that can be expressed as a fraction

```    n / d
```

where n and d are integers and d != 0.

• Thus, a rational number n/d is said to have numerator n and denominator d.

• Denominator is usually kept strictly positive.

• Rational numbers can simplified (normalized or reduced) in case when the numerator and denominator has one or more common factors.

5. Rational number examples

• For example,

• Rational(5,6) represents 5/6

• Rational(5) represents 5/1

• Rational numbers can be reduced to their lowest terms:

• Rational(6,10) can be reduced to 3/5

• Division by zero is obviously not allowed:

• Rational(3,0) yields Zero Division Error at runtime.

6. Rational number approximation

• Rational numbers are used excessively in geometry.

• To yield a floating point approximation of a rational, a method

```
double fraction = Rational(1,3).to_double();

```

could be used.

• For more information, see The Rational Class of the GNU C++ Library programming tools.

7. Binary member operator syntax

• ```
// rational.h
class Rational {
private:
int m_n;
int m_d;
public:
// Member binary operator
bool operator==( Rational const& other) const;
};

// rational.cpp
bool Rational::operator==( Rational const& other ) const
{
// This way, 1/2 == 2/4 (should it?)
return ( ( m_n * other.m_d ) == ( m_d * other.m_n ) );
}

```

8. Unary member operator syntax

• ```
// rational.h
class Rational {
//...
public:
// Unary negation, as in: x = -y
Rational operator-() const;
};

// rational.cpp
Rational Rational::operator-() const
{
// uses 2-argument constructor
return Rational( -m_n, m_d );
}

```

9. Non-member binary operator syntax

• ```
// rational.h
#include <iostream>
using namespace std;
class Rational {
friend ostream& operator<<( ostream& os, Rational const& rat );
//...
};

// rational.cpp
ostream& operator<<( ostream& os, Rational const& rat )
{
os << "(" << rat.m_n << "/" << rat.m_d << ")";
return os;
}

// main.cpp
#include <iostream>
#include "rational.h"
using namespace std;
int main( )
{
Rational r1;
cout << r1 << endl;
return 0;
}

```

10. Available operators

• ```
Binary arithmetic: + - * / %
Unary arithmetic: + -
Binary bitwise: & ^ |
Unary bitwise: ~
Binary logical: && ||
Unary logical: !
Shift (and stream): << >>
Comparison (all binary): == != > < >= <=
Assignment: = += -= *= /= %= &= ^= |= >>= <<=
Increment (two of each): ++, --
Binary structure: []
Unary structure: * ->
Miscellaneous: , () new delete new[] delete[]

```

11. Special operators

• These operators must be members:

```    =   []  ()  and  ->
```

This ensures the first arguments are lvalues (see next slide).

```
operator=   operator&   operator, (comma)

```
• The following operators cannot be overloaded:

```    :: (scope)
.  (member selector)
.* (member selection through pointer to member)
```
• Non-member operators must take at least one user-defined type.

12. lvalue explained

• lvalue is an object that refers to a region of storage in memory that can be both examined and stored into.

• However, lvalue does not necessarily permit modification of the object it designates.

• Thus, lvalue can be

• modifiable lvalue

• non-modifiable lvalue

• For example, a function call that returns a reference is an lvalue.

• Certain operators require lvalues:

```
&X  // Unary address-of operator requires that operand X must be an lvalue.
X++ --X  // Operand X must be an lvalue. This applies to both prefix and postfix forms.
= += -= *= %= <<= >>= &= ^= |=  // Left operand must be an lvalue.

```
• The term rvalue refers to a data value that is stored at some address in memory.

• An rvalue cannot have a new value assigned to it, for example,

• literal constant "Hello" or a const variable.

• When necessary, an lvalue is implicitly converted to an rvalue, but the reverse, however, is not true: an rvalue cannot be converted to an lvalue.

13. Assignment operators

• Should return *this (by reference), to allow for assignment chaining.

• Almost always needs to check for self assignment condition:

```
Rational& Rational::operator=( Rational const& other )
{
if ( this == &other ) // beware of self assignment!
return *this;

m_n = other.m_n;
m_d = other.m_d;

return *this;
}

```

14. operator= and copy constructor

• If you have a copy constructor, you should have an operator=

• Once you have operator=, a copy constructor is very simple:

```
class Rational {
public:
Rational( Rational const& other );
Rational& operator=( Rational const& other);
//...
};

Rational::Rational( Rational const& other )
{
*this = other;
}

```

15. Common uses of operators

• Most classes need =, many need ==, some need >.

• Numerical classes gain natural syntax of arithmetic operators.

• String classes provide + and += for concatenation, and [ ] for character access.

• Lookup structures add operator[ ] taking key types:

```
// PhoneBook.h
class PhoneBook {
public:
string const& operator[]( string const& key );
//...
};

// main.cpp
#include <iostream>
#include "PhoneBook.h"
using namespace std;
int main( )
{
PhoneBook pb;
//...
cout << "Bob's number is " << pb[ "Bob" ] << endl;
return 0;
}

```

16. Increment ++ and decrement -- syntax

• ```
// rational.h
class Rational {
//...
public:
Rational& operator--(); // prefix (result is an lvalue!)
const Rational operator--(int); // postfix (not an lvalue)
};

// rational.cpp
Rational& Rational::operator--()
{
m_n -= m_d;
return *this;
}

const Rational Rational::operator--(int)
{
Rational temp = *this;
m_n -= m_d;
return temp;
}

```

17. The dual operator[] idiom

• It is common to define two operator[ ]s

• Allows use on left-hand side, and on const objects:

```
class MyDoubleArray
{
double& operator[](unsigned int i);
double operator[](unsigned int i) const;
//...
};

void copy( MyDoubleArray& dest, MyDoubleArray const& src )
{
for ( size_t idx = 0; idx < src.size(); ++idx ) {
// Use non-const op[] on left, const op[] on right:
dest[ idx ] = src[ idx ];
}
}

```

18. Conversion by constructor

• Defining a constructor which takes one argument of type T gives conversion from T to your type:

```
// rational.h
class Rational {
private:
int m_n;
int m_d;
public:
Rational( int num ) // convert int to Rational
: m_n(num), m_d(1)
{
}
};

void print_rational( Rational const& rat );

// main.cpp
#include "rational.h"
int main( )
{
Rational rat = 3;  // Ok: same as r(3)
print_rational(3); // Ok: one-level implicit conversion
return 0;
}

```

19. Conversion by constructor mix-up

• Sometimes we are better off without constructor-conversions:

```
class String {
public:
String( int i ) // Innocent idea: set initial size!
{
//...
}
};

String foo()
{
return 0; // probably a typo?!
}

void bar( String const& str )
{
//...
}

int main( )
{
String s1(4);      // OK
String s2 = 4;     // Confusing
String s3 = foo(); // Really??
bar(4);            // Error??
return 0;
}

```
• FYI: the above compiles just fine!

• Better solution: make constructor taking a single size argument explicit.

20. Functors

• operator( ) can only be defined as a member.

• Classes which define operator( ) are functors.

• operator( ) can take any number of arguments

• Use functors in place of function pointers (and vice versa!)

• We will see examples of functors when studying the STL algorithms.

 Consider: ``` #include class BoolFlag { bool m_flag; public: BoolFlag( bool flag ) : m_flag( flag ) { } bool operator|| ( BoolFlag const& other ) { return m_flag || other.m_flag; } }; BoolFlag download( char const* URL ) { std::cout << "Dowloading " << URL << '\n'; //... return BoolFlag( true ); } ``` ``` int main( ) { char const* URL = "www.xyz.com/lib/file.xml"; BoolFlag download_complete = download( URL ); if ( download_complete || download( URL ) ) { // process file... } return 0; } ``` Do not overload operators ``` || and && ``` Clients will expect usual semantics... ...and you can't provide them!
•

22. Do not overload logical operators!

```    operator&&

operator||
```

operators, the operands must be evaluated, which isn't the way things normally work with short circuiting of built-in types.

• This creates programmer astonishment and bugs.

• This is why it is generally bad to overload these operators!

• Use operators in ways which mimic their native use:

1. Don't take or return unexpected types.

2. Make sure you provide a complete set.

• Use named functions at first, build operators on those later.

• Be careful with conversion to other types.

• Define assignment if you define a copy constructor.

• Prefer member functions over non-members for operations that need access to the implementation.

• Prefer non-member functions over members for operations that do not need access to the implementation.