<aside> 🧠Recall
Controller
<aside> đź’ˇ Exception Safety
void f() {
C c;
C *p = new C;
g();
delete p;
}
No leaks… but what if g throws ?
What if guranteed ?
void f() {
C c;
C *p new C;
try {
g();
}
catch(...) {
delete p;
throw;
}
delete p;
}
duplucation code
How else can we gurantee that something (eg. delete p;) will happen, no matter how we exit f ?
In some language “finally” clauses gurantee certain final actions - not in C++
Only thing you can count on in C++ - dtors for stack-allocated deta will run
<aside> đź’ˇ C++ Idiom: RAII - Resource Acquisition Is Initialization
Every resource should be wrapped in a stack-allocated object, whose job is to delete it
eg. Files
{ // ↓ acquiring the resource("file") = initializing the object(f)
ifstream f {"file"}
...
} // - the file is guranteed to be released when f is popped from stack
// (because f's dctor runs)
This can be done with dynamic memory
class std::unique_ptr(T) (import <memory>)
// takes a T* in the ctor
// the dtor will delete the ptr
// in between - can determine, just like a ptr
void f() {
C c;
std::unique.ptr<C> p {new C};
g();
} // - no leaks, guranteed
Alternatives
void f() {
C c;
// allocates a C obj. on the heap, and puts a ptr
// ↓ to it inside a unique.ptr object
auto p = std::make_unique<C>( );
g(); // ↑ ctor args go here, if there are any
}
</aside>
Difficulty:
unique_ptr<C> p {new C};
uniuqe_ptr<C> g = p;
What would happen if a unique.ptr were copied ?
template <typename T> class unique.ptr {
T *ptr;
public:
explicit unique.ptr(T *p): ptr{p} {}
~unique_ptr() {delete ptr;}
unique_ptr(const unique.ptr &other) = delete;
unique.ptr<T> &operator = (const unique_ptr &) = delete;
unique.ptr(unique.ptr &&other): ptr{other.ptr} {
other.ptr = nullptr;
}
unique.ptr<T> & operator(unique.ptr &&other) {
if(this == &other) return *this;
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
return *this;
}
T &operator*() {return *ptr;}
T *get() {return ptr;}
};
If you need to be able to copy ptrs - first answer the quesiton of
Ownership
<aside> đź’ˇ New understanding of ptrs:
<aside> đź«Ą Moving a unique.ptr = transfer of ownership
</aside>
ptrs as parameter
void f(unique_ptr<c>p);
// f will **take ownership** of the object pointed to by p
// - caller **loses custody** of the object
void g(C *p)
// g will **NOT** tale over ownership of the object pointed to by p
// - caller's ownership of the object **does not change**
// (Note that the caller also might not own the obeject)
ptrs as result:
unique.ptr<c> f(); // return by value is always a move
// so f is **handing over ownership** of the C object to the caller
C *g(); // the ptr returned by g is understood not to be deleted
// by the caller. so it might represent a ptr to non-heap data,
// or to heap data that someone else already owns
</aside>
Rarely, a situatoion may arise thay calls for true shared ownership,
i.e. any of several ptrs might need to free the resource
{auto p = std::make_shared<c>();
if(____) {
auto g = p;
} // - g popped, ptr **NOT** deleted
} // - p is popped, ptr **IS** deleted
shared_ptrs maintain a reference const - count of all share.ptr pointing as the same obj
Memory is freed when the # of shared.ptrs pointing to it wil reach 0
<aside> 🧠Recall(Racket)
(define l1 (cons 1 (cons 2 (cons 3 empty))))
(define l2 (cons 4 (rest l1)))
</aside>