Lecture 23
Class unqiue_ptr<T>
has no copy operations, its meant to be unique, so a shallow copy of the ptr is disallowed. They can be from since that signifies the transfer of ownership.
If, however, there is true shared ownership, that is, any of several pointers may need to free the data, then you can use **standard share pointers **
Shared Pointers
shared_ptr
s maintain a reference count, a count of all shared_ptrs
pointing at the same object. The memory is freed when the reference count reaches 0.
Consider the following:
Use the pointer type that accurately reflects the ownership role. Dramatically fewer opportunities for leaks this way.
There are three levels of exception safety for some function f
.
- Basic guarantee: If this function throws or propagates an exception, the program will be in a valid but unspecified state. Valid means that nothing is leaked, and class invariants are maintained. Unspecified means undefined.
- Strong guarantee: If this function throws or propagates an exception, the program will be as if the function was never called.
- No throw guarantee: This function will never throw an exception, and will always complete its task
From the above code, we can ask: “Is C::f
exception safe?
- If
a.g()
throws, nothing happened yet, so ok - If
b.h()
throws, the effects ofa.g()
must be undone to offer the strong guarantee. Very hard or impossible ifa.g()
has non-local side-effects (e.g. printing to output).
Now, assuming there are no non-local side-affects, lets make this exception safe:
Because the copy assignment could throw, we don’t yet have exception safety. It would be better if we could guarantee that the “swap” part was a no-throw operation - a non-throwing swap operation is at the heart of writing exception safe code.
Key observation: Copying pointers can’t throw.
On the other hand, if either A::g
or B::h
offer no exception safety guarantee, then in general, neither can C::f
. Similarly if they have non-local side fix, it is much harder to offer a guarantee for C::f
.
Exception Safety and STL Vectors
Vectors:
- Encapsulate a heal-allocated array
- Follow RAII - with a stack allocated vector goes out of scope, the internal heap allocated array is freed.
Quick review:
But!
So, it you need to free these pointers, would need to do it yourself.
Alternatively, we could make a vector
of unique_ptr
s.
Now, consider how vector<T>::emplace_back
(or push_back) works.
- Offer the strong guarantee
- If the array is full, (i.e size = capacity)
- Doubling, new larger array allocated, copied in
- Adds the new element
- Copies the objects from the old (copy ctor)
- We don’t use move to ensure exception guarantee (say a move operation fails)
- If the copy construction throws, we destroy new larger array, old array still intact, strong guarantee
- delete old array
- Doubling, new larger array allocated, copied in
-
- But**, copying is expensive if old array is just going to be thrown away. Furthermore, how can we create a vector of
unique_ptr
s if they can’t be copied?? wUT..
- But**, copying is expensive if old array is just going to be thrown away. Furthermore, how can we create a vector of
Wouldn’t moving the object from old array to new array be more efficient?
- Allocate new larger array
- Place new object in it
- Move the objects over (move ctor)
- Delete old array
However, emplace_back()
offers the strong guarantee. The problem, if we move, if move ctor throws, then vector<T>::emplace_back
can no longer offer the strong guarantee, since old array is no longer intact. But, emplace_back
promises the strong guarantee, so how does it work? We know it can use move ctor as we can have vector of unique_ptr
, and that class does not offer copy ctor, unique.
Therefore, if the move constructor offer the no throw guarantee, emplace_back
will use the move ctor, otherwise, it will use the copy ctor, may be slower.
This is why we place a new object into the new array first.
Your move operations should provide the no-throw guarantee if possible, and you should indicate that they do.
If you know that a function will not throw or propagate an exception, declare it noexcept
, facilitates optimization. At a minimum, your classes swap and move operations should be non-throwing.