Okay. Simple example, our task is to avoid memory leaks:
int unsafe()
{
A* a = new A;
if (a->f())
{
B* b = new B;
if (b->f())
return b->g();
else
return a->g();
}
return 0;
}
First, assume there are no exceptions. Let's write the corresponding deallocation code:
int unsafe2()
{
A* a = new A;
if (a->f())
{
B* b = new B;
if (b->f())
{
int r = b->g();
delete b;
delete a;
return r;
}
else
{
int r = a->g();
delete b;
delete a;
return r;
}
}
delete a;
return 0;
}
Not only do we need to insert
delete statements, the manual deallocation forces us to break the code flow and store return values in variables instead of returning them directly.
Now we assume that constructors may fail, and attempt to make the code exception-safe and exception-neutral:
int unsafe3()
{
A* a = new A;
if (a->f())
{
B* b;
try
{
b = new B;
}
catch (...)
{
delete a;
throw;
}
if (b->f())
{
int r = b->g();
delete b;
delete a;
return r;
}
else
{
int r = a->g();
delete b;
delete a;
return r;
}
}
delete a;
return 0;
}
We have doubled the original amount of code -- only for deallocation and error handling, we haven't introduced new functionality. And the code is not even safe yet, since the other functions might also throw exceptions. I think you can imagine how complexity further explodes if you implement that.
Code becomes totally unreadable, the actual function logic is lost in boilerplate code.Note also that we use only two dynamic allocations that require such a complicated logic. Imagine how it might look like for 5, or 10. Furthermore, I have only talked about memory leaks. Problems like dangling pointers or double deletes exist, too, just in case life becomes boring.
Now the good news: C++ offers us RAII, a simple yet extremely powerful idiom to manage resources automatically. RAII is not limited to memory, it handles resources in general. What our code looks like with the use of smart pointers:
int safe()
{
std::unique_ptr<A> a(new A);
if (a->f())
{
std::unique_ptr<B> b(new B);
if (b->f())
return b->g();
else
return a->g();
}
return 0;
}
Our code has the same length as the first snippet and is completely safe. We can introduce further
return or
throw statements without touching memory management. Also, we don't get into trouble if one of the called constructors or functions begins to use exceptions.
Code remains very clean, and we have zero performance overhead. Neither speed, nor memory.
As you see, advantages of RAII are far from little. In fact, I don't see a reason not to use basic smart pointers. I understand your personal preference, as you may be used to manual memory management and it worked well so far, but I hope I could show you its hidden problems, and how easy your life could be with RAII