OOP Class Notes for 10/31/13
Operator Overloading
- Java does not support operator overloading.
- C++ does support operator overloading and we will be using this feature in our translator.
- Why can operators be overloaded in C++?
- Because operators are treated as methods in C++ (i.e., you can think of
x + y
asx::operator+(y)
).
- Because operators are treated as methods in C++ (i.e., you can think of
- Advantages for operator overloading:
- flexibility
- convenience (e.g. matrix additions, subtractions, multiplications and inversions)
- simplicity
- concision
- Disadvantages:
- may be confusing if an operator is overloaded for multiple classes
- Same operator symbol would represent different things
- Makes code harder to read and debug
- operators could be overloaded to do something unintuitive (switching + and -)
- may be confusing if an operator is overloaded for multiple classes
Operator overloading in translator
The array subscript operator []
So far in our translator, we need to check to see if the index is within the bounds of the array. Then we can access a specific element in the array
__rt::checkIndex(a, 2); std::cout << "a[2] : " << a->__data[2] << std::endl;
What if we can check if an index is within the bounds of the array while accessing the element in the array. Just like in Java. We can overload the array subscript operator to provide this additional functionality.
T& operator[](int32_t index) { if (0 > index || index >= length) throw ArrayIndexOutOfBoundsException(); // check index return __data[index]; }
So now we can access an element in an array by using the syntax
(*a)[2];
- the [] operator must return a reference to an array element to support modification of the array
- the [] operator always takes an index, so it can check whether the index is in bounds
By convention, when you define the [] operator, you also need to define a const
[] operator. The const
[] operator does not allow modification of a const
array. If the array is const
it will return a const
element.
const T& operator[](int32_t index) const { if (0 > index || index >= length) throw ArrayIndexOutOfBoundsException(); // implementation is exactly the same return __data[index]; }
- If the array is const you get a const element back
- If the array is not const you do not get a const element back (allowing the array access to appear on the left hand side of assignments)
The left-shift operator <<
- Every time we want to print a string, we have to use the code:
cout << k->__vptr->getName(k)->data // k.getName()
- Wouldn't it be nice if we didn't have to use the ->data every time we were printing our string object
- We can do that by overloading the <<operator (left shift operator) in our code.
- The problem is that we cannot (and we shouldn't) edit the standard library
- Since operators are just like methods, they are overloadable.
- We can overload the left-shift operator by writing this code
std::ostream& operator<<(std::ostream& out, String s){ out << s->data; return out; }
ostream
when the String
object is passed in.- Overloading the [] operator is an example of an operator that is a method of a class, the reciever is an instance of that class.
- Overloading the << operator is an example of overloading an operator that is not a member of a class.
A Wrapper Class for Pointers
- To illustrate the power of operator overloading we implement a wrapper class for the built-in pointers of C++
- For now, the wrapper class provides the same functionaly as a regular pointer. However, later on we will extend it with functionality for automatic memory management (smart pointers).
- We made a new template class:
Ptr<T>
whereT
is some type. Ptr<int>
syntax is more intuitive thanint*
- A
Ptr
instance contains an address to aT
since we want it to behave like a real pointer - Constructor: Stores the address
Ptr(T* addr): addr(addr) { TRACE("constructor"); }
- Copy Constructor: Makes a copy of an existing
instance
Ptr(const Ptr& other) : addr(other.addr) { TRACE("copy constructor"); }
- The copy constructor is used when we initilize an instance of a class with another instance
- Destructor: Gets called when the instance is deallocated.
~Ptr() { TRACE("destructor"); }
- If the instance is allocated on the stack, the destructor is called automatically when the instance goes out of scope.
- If the instance is allocated on the heap, the destructor is
called when the instance is explicitly deallocated with
a
delete
statement. - The destructor always implicitly calls the destructor of the super class (assuming there is one) as well as the destructors of all members which have them. If you do not provide a destructor, the compiler provides a default destructor, which does nothing accept for the above implicit destructor calls.
- Overloading the assignment operator (=)
operator=(const Ptr& right) { TRACE("assignment operator"); if (addr != right.addr){ addr = right.addr; } return *this; }
- We overload the assignment operator because we want to make sure that the stored address in the instance is assigned.
- When overloading the assignment operator, you MUST protect against self assignment.
- Overloading the dereference operator (*)
T& operator*() const { TRACE("dereference operator"); return *addr; }
- Overloading the arrow operator (->)
T* operator->() const { TRACE("arrow operator"); return addr; }