Hello again! In this video, we are going to look at writing exception-safe classes. We have been building
up this class, which is similar to the library string, using the principles of RAII, in which we
allocate the resource in the constructor and we release it in the destructor.
Now that we know about exception safety, we want to know: is this class exception safe?
So, in this video, we are going to go throw the member functions of this class and review them for exceptional
safety.
Let's start off with the constructor, which allocates the memory used by this class. Constructors
cannot be declared
noexcept,
So that probably means we cannot provide the no-throw guarantee.
But what can we do?
We have a call to new, which could throw an exception.
If that happens, then the constructor will return immediately, and this object, which is in the process
of being initialized, will be destroyed.
So as far as the rest of the program is concerned, this object never existed. So everything is as it was
before. And we have the strong exception guarantee.
And the same thing, really for the copy constructor.
We have a call to new, which could throw an exception.
And in that case, the object which is being initialized is destroyed.
So again, we have the strong exception guarantee.
For the destructor we call delete, which is a noexcept function, so we can make our destructor
noexcept.
And that means we provide the no-throw guarantee.
So that is actually the strongest guarantee of all.
The assignment operator is a bit more complicated. If you remember, this needs to perform a deep copy.
It is called on an existing object and it needs to release that object's current memory allocation and
give it a new memory allocation.
So again, we have a new call which can throw.
And if that does throw an exception, the function will return immediately.
If it does not modify the data member, then we have the strong exception guarantee, but we have to
be a bit careful here.
So this is the assignment operator implementation we've been using so far.
(I think! It has been revised a few times.) So we have the self-assignment check, then we release the
existing memory allocation.
Then we get a new allocation.
Then we update the size member, and then we copy over the data. And there is actually a problem with
this implementation.
So, can you see what it is?
The problem is that we call delete before we call new.
So if this allocation throws an exception, so if this new call throws an exception, then data will
have the previous value.
But that is now pointing at memory which is no longer valid.
So we have actually modified this object.
We have changed it from having a valid memory allocation to having an invalid allocation.
So what we need to do is to change the order slightly, so we need to allocate the memory before we
release it.
So that means we need to store this memory somewhere, temporarily. Because we still need the old pointer
to call delete.
So then we allocate the memory.
We release the previous memory allocation, and then we reassign the pointer.
So this is all exception-safe and then we do have the strong exception guarantee.
The downside is that very briefly, we are using twice as much memory as we normally do. And, well,
programming in the real
world is all about making trade-offs. Sometimes having exception-safe is more important, and sometimes
keeping down memory usage is more important.
So, it depends on the problem you are solving, really.
So what can we conclude from all this?
When you use RAII, you get a class which is exception safe, almost without doing anything. You just
need to be a little bit careful about the assignment operator.
And in fact, in the video, I will show you another way to do this, which is much easier.
But that is all for this video.
I will see you next time.
Until then, keep coding!