cis 330 c++ and unix · operator overloading syntactic sugar -simply another way of calling a...

48
CIS 330 C++ and Unix Lecture 15 Operator Overloading

Upload: others

Post on 07-Oct-2020

6 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

CIS 330 C++ and UnixLecture 15

Operator Overloading

Page 2: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Operator Overloading

Syntactic sugar - simply another way of calling a function

Instead of arguments appearing inside ( … ), they surround the operator

You can define new operators that work with specific classes

● For example, “adding” two classes may have some semantic meaning

● Define the “+” operator to do this (which will then call a function)

Page 3: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

WarningDo not overuse operator overloading

Only use it if it makes sense, AND makes it easier to read your code

You cannot overload operators that are used with built-in types

● For example, you cannot change the meaning of + in 5 + 10

Page 4: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

DefinitionDefine it like a regular function, but with

operator@

Where @ is the operator you want to overload

Page 5: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Integer {

int i;

public:

Integer(int ii) : i(ii) {}

const Integer operator+(const Integer& rv) const {

Integer(i + rv.i);

}

Integer& operator+=(const Integer& rv) {

i += rv.i;

return *this; // l-value

}

};

int main() {

cout << "built-in types:" << endl;

int i = 1, j = 2, k = 3;

k += i + j;

cout << "user-defined types:" << endl;

Integer ii(1), jj(2), kk(3);

kk += ii + jj;

}

Example

Page 6: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Integer {

int i;

public:

Integer(int ii) : i(ii) {}

const Integer operator+(const Integer& rv) const {

Integer(i + rv.i);

}

Integer& operator+=(const Integer& rv) {

i += rv.i;

return *this; // l-value

}

};

int main() {

cout << "built-in types:" << endl;

int i = 1, j = 2, k = 3;

k += i + j;

cout << "user-defined types:" << endl;

Integer ii(1), jj(2), kk(3);

kk += ii + jj;

}

Example The operator+ produces a new Integer (a temporary) that is used as the rv argument for the operator+=.

The temporary is destroyed when it is no longer needed

Page 7: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Return Value

Member function operator is called for the object on the left-hand side (LHS) of the operator

The argument will be the right-hand side (RHS) of the operator

For non-conditional operators (conditionals usually return a boolean), you will almost always want to return an object, or a reference to an object of the same class/type

● If they are NOT the same type, the interpretation of what is should produce is up to you (e.g., for classes that store charand int, what should char + int produce?)

Page 8: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Overloadable Operators

You can overload almost all the operators in C

● But their use is fairly restrictive. For example, ● you cannot combine operators that have no meaning in C

(e.g., ** to represent exponentiation),● you cannot change the order of evaluation precedence,● you cannot change the number of arguments required

Two methods

● Define it as a global functions (and use friend to allow access)

● Define it as a member function

Page 9: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Integer {

long i;

Integer* This() { return this; }

public:

Integer(long ll = 0) : i(ll) {}

// No side effects takes

// const& argument:

friend const Integer&

operator+(const Integer& a);

friend const Integer

operator-(const Integer& a);

friend const Integer

operator~(const Integer& a);

friend Integer*

operator&(Integer& a);

friend int

operator!(const Integer& a);

// Side effects have non-const&

// argument:

// Prefix:

friend const Integer&

operator++(Integer& a);

// Postfix:

friend const Integer

operator++(Integer& a, int);

// Prefix:

friend const Integer&

operator--(Integer& a);

// Postfix:

friend const Integer

operator--(Integer& a, int);

};

Unary Operators(Global)

Page 10: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

const Integer& operator++(Integer& a) {

cout << "++Integer\n";

a.i++; // a is changed

return a; // a is returned

}

const Integer operator++(Integer& a, int) {

cout << "Integer++\n";

Integer before(a.i);

a.i++; // a is changed

return before; // copy of a before change is returned

}

const Integer& operator--(Integer& a) {

cout << "--Integer\n";

a.i--;

return a;

}

const Integer operator--(Integer& a, int) {

cout << "Integer--\n";

Integer before(a.i);

a.i--;

return before;

}

const Integer& operator+(const Integer& a) {

cout << "+Integer\n";

return a; // Unary + has no effect

}

const Integer operator-(const Integer& a) {

cout << "-Integer\n";

return Integer(-a.i); // Create a new Integer object

}

const Integer operator~(const Integer& a) {

cout << "~Integer\n";

return Integer(~a.i);

}

Integer* operator&(Integer& a) {

cout << "&Integer\n";

return a.This(); // what happens if we make this const?

}

int operator!(const Integer& a) {

cout << "!Integer\n";

return !a.i;

}

Unary Operators(Global)

Page 11: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Byte {

unsigned char b;

public:

Byte(unsigned char bb = 0) : b(bb) {}

const Byte& operator+() const {

cout << "+Byte\n"; return *this;

}

const Byte operator-() const {

cout << "-Byte\n"; return Byte(-b);

}

const Byte operator~() const {

cout << "~Byte\n"; return Byte(~b);

}

Byte operator!() const {

cout << "!Byte\n"; return Byte(!b);

}

Byte* operator&() {

cout << "&Byte\n"; return this;

}

const Byte& operator++() { //pre

cout << "++Byte\n";

b++; return *this;

}

const Byte operator++(int) { //post

cout << "Byte++\n";

Byte before(b);

b++; return before;

}

const Byte& operator--() { //pre

cout << "--Byte\n";

--b; return *this;

}

const Byte operator--(int) { //post

cout << "Byte--\n";

Byte before(b);

--b; return before;

}

};

Unary Operators(Member)

No argument

Why are these different?

Page 12: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

++ and --

You want to be able to call different functions, depending on whether it’s ++a (pre) or a++ (post)

● ++a generate a call to operator++(a)● a++ generate a call to operator++(a, int)● This is done simply to differentiate the functions - the second

int for a++ does not get used

Page 13: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Binary Operators(Global)

class Integer {

long i;

public:

Integer(long ll = 0) : i(ll) {}

// Operators that create new,

// modified value:

friend const Integer

operator+(const Integer& left,

const Integer& right);

friend const Integer

operator<<(const Integer& left,

const Integer& right);

// Assignments modify & return lvalue:

friend Integer&

operator+=(Integer& left,

const Integer& right);

// Conditional operators return true/false:

friend int

operator==(const Integer& left,

const Integer& right);

void print(std::ostream& os) const { os << i; }

};

Page 14: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

// Operators that create new,

// modified value:

const Integer

operator+(const Integer& left,

const Integer& right) {

return Integer(left.i + right.i);

}

const Integer

operator<<(const Integer& left,

const Integer& right) {

return Integer(left.i << right.i);

}

Binary Operators(Global)

// Assignments modify & return lvalue:

Integer& operator+=(Integer& left,

const Integer& right) {

if(&left == &right) {

/* self-assignment */}

left.i += right.i;

return left;

}

// Conditional operators return true/false:

int operator==(const Integer& left,

const Integer& right) {

return left.i == right.i;

}

For example, (a+=1)++; is legal, but (++a)++; is NOT legal in C++

Page 15: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Byte {

unsigned char b;

public:

Byte(unsigned char bb = 0) : b(bb) {}

// No side effects: const member function:

const Byte

operator+(const Byte& right) const {

return Byte(b + right.b);

}

const Byte

operator<<(const Byte& right) const {

return Byte(b << right.b);

}

Binary Operator(Member)

Byte& operator=(const Byte& right) {

// Handle self-assignment:

if(this == &right) return *this;

b = right.b;

return *this;

}

Byte& operator+=(const Byte& right) {

if(this == &right) {/* self-assignment */}

b += right.b;

return *this;

}

int operator==(const Byte& right) const{

return b == right.b;

}

};

Page 16: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

operator= is ONLY allowed to be a member function

Assignments operators (e.g., operator+=) have code to check for self-assignment (although it does not do anything)

● This is a general guideline - there are cases where self-assignment is required (e.g., A+=A to add to itself)

● However, for operator= (depending on what the “=” means) you may have to handle self-assignment as a separate case

Binary Operator

Page 17: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Summary

Unary

● Global vs. Member● friend const Integer operator-(const Integer& a); vs.● const Byte operator-() const

● Differentiate pre- and post- operator using different function definition● friend const Integer& operator++(Integer& a);● friend const Integer operator++(Integer& a, int);

Binary

● Global vs. Member● friend Integer& operator+=(Integer& left, const Integer& right); vs.● Byte& operator+=(const Byte& right);

● operator= is only allowed as a member overloaded function

Page 18: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Arguments and Return Values

You can pass it in any way you want

● You just deal with bugs later

However, the better practice is to restrict what you can do with them depending on what the operator requires

● For example, if you only need to read from the arguments, default to passing it as const reference● Ordinary operators like +, -, conditionals, typically do not

change their arguments, so need to be passed in as const reference

● If they are member functions make it a constmember function

For assignment operators (e.g., +=, =) that change the left-hand argument, the left-hand side is NOT a const

Page 19: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Arguments and Return Values

Type of return value depends on the expected “meaning” of the operation

All assignment operators modify the left-hand value (l-value)

● To allow this to be used in chained expression (e.g., a = b= c;), it is expected that reference to the l-value that was modified is returned

● Since a = b = c; is read from left to right by the compiler, you CAN have it return const, but if you want to perform an operation on it (e.g., (a = b).func(); to call func() on a after assigning b to it), the return value should be non-const reference (remember that you can’t call non-constmember functions on a const object).

For logical operators, everyone expects int at worst, and bool at best

Page 20: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

More on Return Value as const

Consider func(a + b)

● a + bwill be automatically stored as a const because it is a temporary - so making the return value constmay seem redundant

● Also, you may want to do (a + b).func2()● Now, only a const function would be executed if the return

value is const● This is actually the correct thing to do - why?

● (a + b) isn’t explicitly stored anywhere - so this prevents you from storing potentially valuable information on an object that will likely be lost

● For example

(a + b).func2(); // increment the result by 1

(a + b).func2(); // increment the result by 1

What would be the end result?

Page 21: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Return Value Optimization

return Integer(left.i + right.i);

● This is NOT a function call to a constructor (we have seen this format before in aggregate init)

● This actually means, make a temporary Integer object and return it

● This different fromInteger tmp(left.i + right.i);return tmp;

● tmp object is created using its constructor -> copy-constructor copies its value to where the return value is stored -> destructor is called for tmp

● This is less efficient than the first method● Compiler directly creates the object into the return value

location (i.e., 1 constructor call, no copy-constructor, no destructor)

Page 22: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Unusual Operators

operator[]must be a member function

● Object being called is assumed to act like an array, so you will often return a reference

● Thus, it can be used on the LHS of an equal sign

new and delete can also be overloaded in different ways

Page 23: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Unusual Operators

operator->

Generally used to make the object appear to be a pointer

Overloading typically results in adding more functionality to what it usually means, and is often referred to as a “smart pointer”

Used to implement iterators, that allows you to move through a collection of objects one at a time without providing direct access (i.e., safer)

Also must be a member function

Page 24: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class SmartPointer {

ObjContainer& oc;

int index;

public:

SmartPointer(ObjContainer& objc) : oc(objc) {

index = 0;

}

// Return value indicates end of list:

bool operator++() { // Prefix

if(index >= oc.a.size()) return false;

if(oc.a[++index] == 0) return false;

return true;

}

bool operator++(int) { // Postfix

return operator++(); // Use prefix version

}

Obj* operator->() const {

require(oc.a[index] != 0, "Zero value "

"returned bySmartPointer::operator->()");

return oc.a[index];

}

};

class Obj {

static int i, j;

public:

void f() const { cout << i++ << endl; }

void g() const { cout << j++ << endl; }

};

int Obj::i = 47;

int Obj::j = 11;

// Container:

class ObjContainer {

vector<Obj*> a;

public:

void add(Obj* obj) { a.push_back(obj); }

friend class SmartPointer;

};

Example

Page 25: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

int main() {

const int sz = 10;

Obj o[sz];

ObjContainer oc;

for(int i = 0; i < sz; i++)

oc.add(&o[i]); // Fill it up

SmartPointer sp(oc); // Create an iterator

do {

sp->f(); // Pointer dereference operator call

sp->g();

} while(sp++);

} ///:~

Example

Page 26: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Class Obj is as beforeclass ObjContainer {

vector<Obj*> a;

public:

void add(Obj* obj) { a.push_back(obj); }

class SmartPointer;

class SmartPointer {

ObjContainer& oc;

unsigned int index;

public:

SmartPointer(ObjContainer& objc) : oc(objc) {

index = 0;

}

// Return value indicates end of list:

bool operator++() { // Prefix

if(index >= oc.a.size()) return false;

if(oc.a[++index] == 0) return false;

return true;

}

bool operator++(int) { // Postfix

return operator++();

}

Obj* operator->() const {

require(oc.a[index] != 0, "Zero value "

"returned by SmartPointer::operator->()");

return oc.a[index];

}

};

// Function to produce a smart pointer that points to the beginning of the ObjContainer:

SmartPointer begin() {

return SmartPointer(*this);

}

};

Nested Iterator

Page 27: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

int main() {

const int sz = 10;

Obj o[sz];

ObjContainer oc;

for(int i = 0; i < sz; i++)

oc.add(&o[i]); // Fill it up

ObjContainer::SmartPointer sp = oc.begin();

// SmartPointer sp(oc);

do {

sp->f(); // Pointer dereference operator call

sp->g();

} while(++sp);

} ///:~

Nested Iterator

Page 28: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

In C, you can define function pointers

void qsort(void *base, size_t nmemb, size_t size,int(*compar)(const void*, const void*));

int int_sorter(const void *first_arg, const void *second_arg)

{

int first = *(int*)first_arg;

int second = *(int*)second_arg;

if ( first < second )

return -1;

else if ( first == second )

return 0;

else

return 1;

}

qsort(base, n, size, &int_sorter);

typedef and function pointers

Page 29: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

typedef and function pointers

Similar in C++, you can make this a bit cleanervoid qsort(void *base, size_t nmemb, size_t size,

int(*compar)(const void*, const void*));int (*comp) (const void*, const void*) = &int_sorter;

Can be written as

typedef int (*compFnc)(const void*, const void*);

void qsort(void *base, size_t nmemb, size_t size, compFnc comp);

compFnc comp = &int_sorter;

qsort(base, n, size, comp);

Page 30: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

operator->*class Dog {

public:

int run(int i) const {

cout << "run\n";

return i;

}

int eat(int i) const {

cout << "eat\n";

return i;

}

int sleep(int i) const {

cout << "ZZZ\n";

return i;

}

typedef int (Dog::*PMF) (int) const;

class FunctionObject {

Dog* ptr;

PMF pmem;

public:

FunctionObject(Dog* wp, PMF pmf)

: ptr(wp), pmem(pmf) {

cout << "FunctionObject constructor\n";

}

int operator()(int i) const {

cout << "FunctionObject::operator()\n";

return (ptr->*pmem)(i); // Make the call

}

};

FunctionObject operator->*(PMF pmf) {

cout << "operator->*" << endl;

return FunctionObject(this, pmf);

}

};

Unusual Operators

Page 31: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

int main() {

Dog w;

Dog::PMF pmf = &Dog::run;

cout << (w->*pmf)(1) << endl;

pmf = &Dog::sleep;

cout << (w->*pmf)(2) << endl;

pmf = &Dog::eat;

cout << (w->*pmf)(3) << endl;

} ///:~

Page 32: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Other Operators

You cannot overload the following operators:

● operator. - is used to access ANY member of a class -overwriting it may prevent it from being used to access some members

● operator.* - same reasons● operator** - C doesn’t have exponentiation, and it’s difficult

to parse

You also can’t make up your own operators or change precedence rules

Page 33: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Non-member Operators

Generally, you should use member function for operators to emphasize the association between the operators and its class

● What if we want the left-hand operand to be an object of ANOTHER class?● You have no choice but to define a non-member operator● Common use case is for << and >> overloaded for iostream

Recommended Usage:

All unary member

= () [] -> ->* MUST be member

+= -= /= … member

All other binary operators non-member

Page 34: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

ostream&

operator<<(ostream& os, constIntArray& ia) {

for(int j = 0; j < ia.sz; j++) {

os << ia.i[j];

if(j != ia.sz -1)

os << ", ";

}

os << endl;

return os;

}

istream& operator>>(istream& is, IntArray& ia){

for(int j = 0; j < ia.sz; j++)

is >> ia.i[j];

return is;

}

class IntArray {

enum { sz = 5 };

int i[sz];

public:

IntArray() { memset(i, 0, sz* sizeof(*i)); }

int& operator[](int x) {

require(x >= 0 && x < sz,

"IntArray::operator[] out of range"); // range checking

return i[x];

}

friend ostream&

operator<<(ostream& os, constIntArray& ia);

friend istream&

operator>>(istream& is, IntArray& ia);

};

Example

Page 35: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Example

int main() {

stringstream input("47 34 56 92 103");

IntArray I;

input >> I;

I[4] = -1; // Use overloaded operator[]

cout << I;

} ///:~

Page 36: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

More on =

MyType b;

MyType a = b;

a = b;

What happens for line 2?

● Remember that C++ will initialize the object when it is defined (but memory allocated at beginning of its scope)

● Is it using the regular constructor or the copy-constructor (since it’s defined as equal to b) - it uses the copy constructor since it’s being created for the first time

For line 3,

● regular operator= is called (since a already exists)

Avoid using = for initialization an object (use the constructor, if at all possible)

● Any time an object is initialized with “=” (instead of the ordinary constructor form), the compiler will look for a constructor that accepts the RHS operand of “=” (e.g., copy-constructor)

Page 37: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Fi {

public:

Fi() {}

};

class Fee {

public:

Fee(int) {}

Fee(const Fi&) {}

};

int main() {

Fee fee = 1; // actually Fee(int) is called

Fi fi;

Fee fum = fi; // Fee(Fi)

}

Example

Page 38: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

More on =When you create a operator= make sure to copy all necessary information from the RHS object into the current object

Return the reference, so more complex expressions can be created

Always check for self-assignment

Page 39: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Value {

int a, b;

float c;

public:

Value(int aa = 0, int bb = 0, float cc = 0.0)

: a(aa), b(bb), c(cc) {}

Value& operator=(const Value& rv) {

a = rv.a;

b = rv.b;

c = rv.c;

return *this;

}

friend ostream&

operator<<(ostream& os, const Value& rv) {

return os << "a = " << rv.a << ", b = "

<< rv.b << ", c = " << rv.c;

} };

Example

int main() {

Value a, b(1, 2, 3.3);

cout << "a: " << a << endl;

cout << "b: " << b << endl;

a = b;

cout << "a after assignment: " << a << endl;

} ///:~

What would be printed?

Page 40: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

More on =What about pointers in the class?

● Do I copy the just the pointer or the data pointed to by the pointer?

● This depends on usage, but simplest solution is to make another copy of the data pointed to by the pointer

Page 41: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Reference Counting

If your objects require a lot of resources to initialize, avoid copying it with (operator=)

Common approach to doing this is called “reference counting”

The object that’s being pointed to (instead of copied) keeps track of how many objects are pointing to it

Copying (i.e., using copy-constructor) is attaching another point to an existing object and incrementing the reference count

Destruction is reducing the reference count, and only use the destructor when the reference count goes to zero

What if you want to actually write to the object?

● copy-on-write ● If you are the only one pointing to it, go ahead and

write● If not, create a personal copy of it, then change it

Page 42: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Lastly

If you don’t define an operator=, the compiler creates one for you

Behavior mimics the automatically created copy-constructor - if the class contains objects, the operator= for those objects is called recursively (those may also have been automatically created)

● This is also known as member-wise assignment

Page 43: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Automatic Type Conversion

If the compiler sees an expression or function using a type that isn’t quite what it needs, it can often perform automatic type conversion

In C++ you can achieve the same for user-defined types (i.e., classes), by defining automatic type conversion functions, using

A particular type of constructor

Overloaded operator

Page 44: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Automatic Type Conversion

Constructor conversionclass One {

public:

One() {}

};

class Two {

public:

Two(const One&) {}

};

void f(Two) {}

int main() {

One one;

f(one); // Wants a Two, has a One

} ///:~

Page 45: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Preventing constructor conversionclass One {

public:

One() {}

};

class Two {

public:

explicit Two(const One&) {}

};

void f(Two) {}

int main() {

One one;

//! f(one); // No auto conversion allowed

f(Two(one)); // OK -- user performs conversion

} ///:~

Automatic Type Conversion

Page 46: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Operator Conversionclass Three {

int i;

public:

Three(int ii = 0, int = 0) : i(ii) {}

};

class Four {

int x;

public:

Four(int xx) : x(xx) {}

operator Three() const { return Three(x); }

};

void g(Three) {}

int main() {

Four four(1);

g(four);

g(1); // Calls Three(1,0)

} ///:~

Automatic Type Conversion

Page 47: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

class Number {

int i;

public:

Number(int ii = 0) : i(ii) {}

const Number

operator+(const Number& n) const {

return Number(i + n.i);

}

friend const Number

operator-(const Number&,

const Number&);

};

Reflexivity

With global operators conversion can be applied to either operands

● Whereas with member conversion, the LHS operand must be of the proper type

const Numberoperator-(const Number& n1,

const Number& n2) {return Number(n1.i - n2.i);

}int main() {

Number a(47), b(11);a + b; // OKa + 1; // 2nd arg converted to

Number//! 1 + a; // Wrong! 1st arg not of type Number

a - b; // OKa - 1; // 2nd arg converted to

Number1 - a; // 1st arg converted to

Number} ///:~

Page 48: CIS 330 C++ and Unix · Operator Overloading Syntactic sugar -simply another way of calling a function Instead of arguments appearing inside ( … ), they surround the operator You

Pitfalls

Since the compiler “quietly” performs the conversion, if you don’t design the conversion properly, you might introduce a bug

● For example, if there are two ways to convert (constructor AND operator overload), you have ambiguous conversion

class Orange; // Class declaration

class Apple {

public:

operator Orange() const; // Convert Apple to Orange

};

class Orange {

public:

Orange(Apple); // Convert Apple to Orange

};

void f(Orange) {}

int main() {

Apple a;

//! f(a); // Error: ambiguous conversion

} ///:~