COMP6771 Week 4.1
Operator Overloading
Start with an example
- Line 26 is our best attempt to "Add two points together and print them"
print(std::cout, Point::add(p1, p2));
- This is clumsy and ugly. We'd much prefer to have a semantic like this
std::cout << p1 + p2;
#include <iostream>
class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
const int& x() const { return this->x_; };
const int& y() const { return this->y_; };
static Point add(const Point& p1, const Point& p2);
private:
int x_;
int y_;
};
void print(std::ostream& os, const Point& p) {
os << "(" << p.x() << "," << p.y() << ")";
}
Point Point::add(const Point& p1, const Point& p2) {
return Point{p1.x() + p2.x(), p1.y() + p2.y()};
}
int main() {
Point p1{1, 2};
Point p2{2, 3};
print(std::cout, Point::add(p1, p2));
std::cout << "\n";
}
Start with an example
#include <iostream>
#include <ostream>
class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
const int& x() const { return this->x_; };
const int& y() const { return this->y_; };
static Point add(const Point& p1, const Point& p2);
private:
int x_;
int y_;
};
void print(std::ostream& os, const Point& p) {
os << "(" << p.x() << "," << p.y() << ")";
}
Point Point::add(const Point& p1, const Point& p2) {
return Point{p1.x() + p2.x(), p1.y() + p2.y()};
}
int main() {
Point p1{1, 2};
Point p2{2, 3};
print(std::cout, Point::add(p1, p2));
std::cout << "\n";
}
#include <iostream>
#include <ostream>
class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
friend Point operator+(const Point& lhs, const Point& rhs);
friend std::ostream& operator<<(std::ostream& os,
const Point& p);
private:
int x_;
int y_;
};
Point operator+(const Point& lhs, const Point& rhs) {
return Point{lhs.x_ + rhs.x_, lhs.y_ + rhs.y_};
}
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x_ << "," << p.y_ << ")";
return os;
}
int main() {
Point p1{1, 2};
Point p2{2, 3};
std::cout << p1 + p2 << "\n";
}
Operator Overloading
- C++ supports a rich set of operator overloads
- All operator overloads must have at least one operand of its type
- Advantages:
- Reuse existing code semantics
- No verbosity required for simple operations
- Disadvantages:
- Lack of context on operations
- Only create an overload if your type has a single, obvious meaning to an operator
Operator Overload Design
Type | Operator(s) | Member / friend |
---|---|---|
I/O | <<, >> | friend |
Arithmetic | +, -, *, / | friend |
Relational, Equality | >, <, >=, <=, ==, != | friend |
Assignment | = | member (non-const) |
Compound assignment | +=, -=, *=, /= | member (non-const) |
Subscript | [] | member (both) |
Increment/Decrement | ++, -- | member (non-const) |
Arrow, Deference | ->, * | member (both) |
Call | () | member |
- Use members when the operation is called in the context of a particular instance
- Use friends when the operation is called without any particular instance
- Even if they don't require access to private details
Overload: I/O
// Point.h:
#include <ostream>
#include <istream>
class Point {
public:
Point(int x, int y) : x_{x}, y_{y} {};
friend std::ostream& operator<<(std::ostream& os, const Point& type);
friend std::istream& operator>>(std::istream& is, Point& type);
private:
int x_;
int y_;
};
// Point.cpp:
#include <ostream>
#include <istream>
#include <oistream>
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x_ << "," << p.y_ << ")";
return os;
}
std::istream& operator>>(std::istream& is, Point& type) {
// To be done in tutorials
}
int main() {
Point p{1,2};
std::cout << p << '\n';
}
Overload: Compound assignment
// Point.h:
class Point {
public:
Point& operator+=(const Point& p);
Point& operator-=(const Point& p);
Point& operator*=(const Point& p);
Point& operator/=(const Point& p);
Point& operator*=(const int& i);
private:
int x_;
int y_;
};
// Point.cpp:
Point& Point::operator+=(const Point& p) {
this->x_ += p.x_;
this->y_ += p.y_;
return *this;
}
Point& Point::operator-=(const Point& p) { /* Should we do this one? */ }
Point& Point::operator*=(const Point& p) { /* Should we do this one? */ }
Point& Point::operator/=(const Point& p) { /* Should we do this one? */ }
Point& Point::operator*=(const int& p) { /* Should we do this one? */ }
Overload: Relational & Equality
// Point.h:
class Point {
public:
// hidden friend - preferred
friend bool operator==(const Point& p1, const Point& p2) {
return p1.x_ == p2.x_ && p1.y_ == p2.y_;
// return std::tie(p1.x_, p1.y_) == std::tie(p2.x_, p2.y_);
}
friend bool operator!=(const Point& p1, const Point& p2) {
return !(p1 == p2);
}
friend bool operator<(const Point& p1, const Point& p2) {
// Do we want this? Alternatives?
}
friend bool operator<=(const Point& p1, const Point& p2);
friend bool operator>(const Point& p1, const Point& p2);
friend bool operator>=(const Point& p1, const Point& p2);
private:
int x_;
int y_;
};
Overload: Assignment
// Point.h:
#include <istream>
class Point {
public:
Point& operator=(const Point& p);
Point& operator=(std::istream &is);
private:
int x_;
int y_;
};
// Point.cpp:
#include <istream>
Point& Point::operator=(const Point& p) {
this->x_ = p.x_;
this->y_ = p.y_;
return *this;
}
Point& Point::operator=(std::istream &is) {
// etc
}
Overload: Subscript
// Point.h:
class Point {
public:
int& operator[](int i); // setting via []
int operator[](int i) const; // getting via []
private:
int x_;
int y_;
};
// Point.cpp:
#include <cassert>
int& Point::operator[](int i) {
assert(i == 0 || i == 1);
if (i == 0) return this->x_;
else return this->y_;
};
int Point::operator[](int i) const {
assert(i == 0 || i == 1);
if (i == 0) return this->x_;
else return this->y_;
};
- Usually only defined on indexable containers
- Different operator for get/set
- Asserts are the right approach here as preconditions:
- In other containers (e.g. vector), invalid index access is undefined behaviour. Usually an explicit crash is better than undefined behaviour
- Asserts are stripped out of optimisation builds
Overload: Increment/Decrement
// RoadPosition.h:
class RoadPosition {
public:
RoadPosition(int km) : km_from_sydney_(km) {}
RoadPosition& operator++(); // prefix
// This is *always* an int, no
// matter your type.
RoadPosition operator++(int); // postfix
void tick();
int km() { return km_from_sydney_; }
private:
void tick_();
int km_from_sydney_;
};
// RoadPosition.cpp:
#include <iostream>
RoadPosition& RoadPosition::operator++() {
this->tick_();
return *this;
}
RoadPosition RoadPosition::operator++(int) {
RoadPosition rp = *this;
this->tick_();
return rp;
}
void RoadPosition::tick_() {
++(this->km_from_sydney_);
}
- prefix: ++x, --x, returns lvalue reference
- postfix: x++, x--, returns rvalue
- Performance: prefix > postfix
- Different operator for get/set
- Postfix operator takes in an int
- This is not to be used
- It is only for function matching
- Don't name the variable
int main() {
RoadPosition rp{5};
std::cout << rp.km() << '\n';
int val1 = (rp++).km();
int val2 = (++rp).km();
std::cout << val1 << '\n';
std::cout << val2 << '\n';
}
Overload: Arrow & Dereferencing
#include <iostream>
class StringPtr {
public:
StringPtr(std::string *p) : ptr{p} { }
~StringPtr() { delete ptr; }
std::string* operator->() { return ptr; }
std::string& operator*() { return *ptr; }
private:
std::string *ptr;
};
int main() {
std::string *ps = new std::string{"smart pointer"};
StringPtr p{ps};
std::cout << *p << std::endl;
std::cout << p->size() << std::endl;
}
- Classes exhibit pointer-like behaviour when -> is overloaded
- For -> to work it must return a pointer to a class type or an object of a class type that defines its own -> operator
- Interesting example: std::optional
Overload: Other
- Many other operator overloads
- Full list here: https://en.cppreference.com/w/cpp/language/operators
- Example: <type> overload
// Point.h:
#include <vector>
class Point {
public:
Point(int x, int y) : x_(x), y_(y) {}
operator std::vector<int>() {
std::vector<int> vec;
vec.push_back(x_);
vec.push_back(y_);
return vec;
}
private:
int x_;
int y_;
};
// Point.cpp:
#include <iostream>
#include <vector>
int main() {
Point p{1,2};
std::vector<int> vec = static_cast<std::vector<int>>(p);
std::cout << vec[0] << '\n';
std::cout << vec[1] << '\n';
}
COMP6771 19T2 - 4.1 - Operator Overloading
By cs6771
COMP6771 19T2 - 4.1 - Operator Overloading
- 840