Recall: Iterator Pattern
class List {
struct Node;
Node *theList;
public:
class Iterator {
Node *p;
public:
Iterator(Node *p) p {p} {}
int &operator *() {return p->data;}
bool operator!=(const Iterator &other) const {return p!=other.p;}
};
Iterator begin(){return Iterator {theList};}
Iterator end(){return Iterator {nullptr};}
...
};
[client]
List l;
l.addToFront(1);
l.addToFront(2);
l.addToFront(3);
// |-------------| auto
for(List::Iterator it = l.begin(); it != l.end(); ++it) {
cout << *it << endl;
} // O(n)
Shortcut: range-based for heap
for(auto n:l) {
cout << n << endl;
}
<aside> đź’ˇ Available for any class with
If you want to modify list items (or save copying)
for(auto &n:l) {
++n;
}
<aside> 💡 Encapsulation ct’d
We could - make Iterator’s ctor private
Sol’n:
class List {
...
public:
class Iterator {
Node *p;
Iterator(Node *p);
public:
...
...
friend class List;
// List has access to all members of Iterator.
// Node: doesn't matter where in class Iterator you put this
};
}
Now List can still create Iterator, but client can only create Iterator by calling begin or end
Give your classes as few friends as possible - weakens encapsulation
Providing access to private fields: accessor/mutator methods
class Vec {
int x, y;
public:
...
int getX()const {return x;} // accessor
void setY(int x) {y = z;} // mutator
};
What about operator << - needs x, y but can't be a member
class Vec {
...
public:
friend ostream &operator<<(ostream &out, const Vec &v);
}; // ↑ non-member f'n
ostream &operator<<(ostream &out, const Vec &v){
return out << v.x << ' ' << v.y;
} // has access to Vec fields
<aside> đź’ˇ Equality Revisited
Suppose we wat to add a length() method to List How should we implement it?
Options
But consider again the spaceship operator <=> in the special case of equality checking
l1 == l2 translates to (l1 <=> l2) == 0
What is the cost of <=> on two lists? O(length of shorter list)
But for equality checking, we missed a shortcut: lists whose lengths are different cannot be equal. In this case, we could answer “not equal” in O(1) time.
</aside>
class List {
Node *theList;
public:
auto operator<=>(const &other) const {
if(!theList && !other.theList) return std::strong_ordering::equal;
if(!theList) return std::strong_ordering::less;
if(!other.theList) return std::strong_ordering::greater;
return *theList <=> *other.theList;
}
// asking specifically length are equal
bool operator==(const List &other) const {
if(length != other.length) return false; // O(1)
return (*this <=> other) == 0;
}
};
Operator <=> gives automatic impl’s to all 6 relational operators, but if you write operator== separately, the compiler will use that for both == and != instead of <=> Let’s you optimize your equality checking if possible
<aside> đź’ˇ System Modelling
Visualize the structure of the system (abstractions & relationships among them) to aid design, implementation, communication
Popular standard: UML (Unified Modelling Language)
Modelling classes: -pricate, +public
graph TD
Vec --> B("-x:Integer"<br> -y:Integer)
--> C("+getX:Integer"<br>+getY:Integer)
</aside>
<aside> đź’ˇ Relationship: Composiiton of classes
class Basis {
Vec v1, v2;
};
Embedding one object(Vec) inside another(Basis) called composition
A Basis is composed of 2 Vecs. They are part of a Basis, and that is the only purpose of these Vecs
Relationships: a Basis “own a” Vec (in fact, it owns 2 of them)
If A “owns a” B, then typically,
eg. A car owns its engine - the engine is part of the car
Implementation: usually as composition of classes
</aside>
<aside> đź’ˇ Aggregation
Compare car parts in a car (”owns a”) vs. car parts in a catalogue
The catalofue contains the parts, but the parts exist on their own.
“has a” relationship(aggregation)
If A “has a” B then typically,
</aside>
#ifndef TIER_LIST_H
#define TIER_LIST_H
#include "list.h"
#include <string>
class TierList {
List **tiers;
size_t tierCount;
size_t reserved;
void swap(TierList &other);
void enlarge();
public:
// Default constructor and destructor for a TierList.
// The default constructor should initalize an empty tier list.
TierList();
~TierList();
// Adds/removes a tier at the end of the tier list.
// Tiers are indexed starting at 0. Runs in time
// at most _linear in the number of tiers_, but _not_ in the number
// of elements.
void push_back_tier();
void pop_back_tier();
// Adds/removes an element at the front of the given tier.
// Must run in constant time.
void push_front_at_tier(size_t tier, const std::string &entry);
void pop_front_at_tier(size_t tier);
// Returns the number of tiers. Runs in constant time.
size_t tierSize() const;
// Returns the number of elements. Can run in time
// up to linear in the number of tiers.
size_t size() const;
public:
struct value_type {
size_t tier;
std::string entry;
};
class iterator {
friend class TierList;
// Fill in whatever private fields your iterator class needs.
iterator(/* Fill in whatever arguments you need */);
public:
// Returns a value_type instance, containing
// - a) the tier the item that the iterator points to lives at.
// - b) the item the iterator points to
value_type operator*() const;
iterator &operator++();
// New iterator operators which return a iterator pointing to the
// start of the tier bk elements behind/fwd elements later
// of the tier the iterator is currently on.
//
// If said tier is empty, the iterator moves back/forward to the next
// non-empty tier.
iterator operator<<(int bk) const;
iterator operator>>(int fwd) const;
bool operator!=(const iterator &other) const;
};
iterator begin() const;
iterator end() const;
};
#endif
Node *theList = nullptr;
iterator(Node *theList);