<< A big unit on Object Oriented Design | Home | >> Abstraction over Iterators
2017-11-16
Is C++ hard? (No, if you're a client programmer)
But explicit memory management...
- If you need an array, use
vector
- If you need a heap object, use
unique_ptr
- Use stack-allocated objects as much as possible
If you follow these you should never have to say delete
or delete[]
But unique_ptr
doesn't respect is-a:
unique_ptr<Base> p(new Derived); // Ok
p->virtfn(); // Runs derived version, Ok
BUT
unique_ptr<Derived> q = ...
unique_ptr<Base> p = std::move(q); // Wrong
Type error, no conversion between unique_ptr<Derived>
and unique_ptr<Base>
Easy to fix:
template<typename T> class unique_ptr {
private:
T *p;
...
public:
template<typename U> unique_ptr(unique_ptr<U> &&q): p{q.p} {
q.p = nullptr;
}
template<typename U> unique_ptr &operator=(unique_ptr<U> &&q) {
std::swap(p, q.p); // T and U are mutually assignable
return *this;
}
};
Works for any unique_ptr
whose pointer is assignment compatible with this->p
Ex. Subtypes of T
, but not supertypes of T
"But I want two smart pointers pointing at the same object!"
- Why? The pointers that owns the object should be a
unique_ptr
- All others should be raw pointers
When would you want true shared ownership?
Recall in Racket:
(define l1 (cons 1 (cons 2 (cons 3 empty))))
(define l2 (cons 4 (rest l1)))
+---+---+ +---+---+ +---+---+
l1 | 1 | ------>| 2 | ----->| 3 | \ |
+---+---+ +---+---+ +---+---+
^
+---+---+ |
l2 | 1 | ---------+
+---+---+
Shared data structures are a nightmare in C. How can we ensure each node is freed exactly once?
Easy in garbage collected languages.
What can C++ do?
template <typename T> class shared_ptr {
T *p;
int *refcount;
public:
...
};
-
refcount
counts how manyshared_ptr
s point at*p
. -
Updated each time a
shared_ptr
is initialized/assigned/destroyed -
refcount
is shared among all shared pointers that point to*p
-
p
is only deleted if itsrefcount
reaches0
-
implementation details - left to you
struct Node {
int data;
shared_ptr<Node> next;
};
Now deallocation is as easy as garbage collection
Just watch: cycles
+---+---+ +---+---+
| 1 | ------>| 2 | -----+
+---+---+ +---+---+ |
^ |
| |
+---------------------+
If you have cyclic data, you may have to physically break the cycle (or use weak_ptrs
)
Also watch:
Book *p = new ...
shared_ptr<Book> p1 {p};
shared_ptr<Book> p2 {p}; // Will compile but NO
shared_ptr
s are not mind-readersp1
andp2
will not share arefcount
(BAD)- If you want 2
shared_ptr
s at an object, create oneshare_ptr
and copy it
BUT
You can't dynamic_cast
these pointers
template<typename T, typename U>
shared_ptr<T> dynamic_pointer_cast(const shared_ptr<U> &spu) {
return shared_ptr<T>(dynamic_cast<T*>(spu.get()));
}
Similarily const_pointer_cast
, static_pointer_cast
<< A big unit on Object Oriented Design | Home | >> Abstraction over Iterators