Smart pointers have become the premium choice for memory management in C++. This is everything but odd, when you come to think of it.
They are lightweight, memory management is as tight as it could be and they are very simple to use. The pitfalls exist but they are pretty obvious (such as: no dark magic on the internal pointer...).
And it's transparent! Look, I type "->" and I really access my pointer!
Last but not least, since the TR1, smart pointers are available as part of the STL, making their usage safe and standard compliant.
That being said, I think they are overused.
Smart pointers can be categorized into two different broad categories, based on their scope of action: local or global.
A scoped pointer will release the attached resource when exiting the current scope. This may be a function body, a class, or a loop. Copying a scoped pointer to be used outside the scope results in undefined behaviour.
Examples:
These smart pointers are straightforward and are typically used to manage resources you don't wish to share outside any given considered scope. They were designed to make RAII easier and uniform.
The attached resource will be released when there are no more owners. This is done - most of the time - with references counting: the smart pointer embodies a reference counter that is increased when ownership is taken and decreased when ownership is released. Thus shared pointers can be copied and used outside the instantiation scope.
Examples:
Taking ownership means "getting a copy of the smart pointer object". Whether this is through the copy constructor or the affectation operator makes no difference.
Shared smart pointers are a great way to avoid memory leaks as the resource will be freed as soon as possible.
Should you wish to know more about smart pointers and memory management, I invite you to read this previous post.
First and foremost because they are unneeded. In C++ it's easy to avoid dynamic allocation, do it whenever you can.
Does it mean that when you really need a dynamically allocated object, you must use a smart pointer? The answer is no, for at least two reasons : interfaces coupling and performances.
Your interfaces shouldn't return a smart pointer. You don't want to expose your memory management strategy to the outside to prevent the clients from making any assumption. Clients making assumptions are the seed of maintenance hell.
You want the smallest possible contact surface between two interfaces. Using smart pointers increases the surface, therefore you should refrain from using them.
Thus, that kind of interface is to be avoided:
1
2 3 4 5 |
class interface
{ void apply thing(sharedptr < obj > o ) ; shared ptr<obj> dothing (shared_ptr <obj > o ) ; } ; |
Instead, prefer:
1
2 3 4 5 |
class interface
{ void apply thing(obj &o); obj &dothing (obj &o ) ; } ; |
What's the difference? In the first case you impose a memory management model to your client, in the second you don't. In the first case you impose a dependency on shared_ptr, in the second you don't.
If the underlying objects are dynamically allocated and you cannot pass along references, you might want to consider:
1
2 3 4 5 |
class interface
{ void apply thing(obj *o); obj *dothing (obj *o ) ; } ; |
That looks dangerous, but it's not, as long as you document clearly how resources should be released. That way, your client may encapsulate (or not) the provided pointers in the most convenient way for the considered application. You can even provide a proposed encapsulation to your client.
If you force the usage of shared pointers (for example), your client cannot directly wrap the object into a scoped pointer. Or maybe your client has got its own smart pointer class and wants to use it. Or maybe your client prefers to work on raw pointers for efficiency reasons. I'm pretty sure you can come up with reasons of your own.
Wait a minute... Did someone say efficiency reasons?
Surprised? How could increasing and decreasing an integer affect performances on a multi-core multi-gigahertz computer? Am I being obsessed by details? Have I done too much assembly in my life to think straight?
Sounds like a terrible picture, isn't it? Fortunately, it's not that bad. The performance penalty really start to appear during the pointer migration season (that's around Fall), when all the pointers go to the south of memory, where it's warmer. Rest of the year it's pretty ok...
Just keep that list in mind for a while and use it as a reference for the rest of the post.
Most of the time you are keeping a count you care little about. Who cares how many owners they are if this information is unnecessary to decide when a resource should be released?
A typical example:
Now consider:
What happens here is that we have used a shared pointer when a scoped pointer was enough. That mistake is made more often that not, and that's really a waste of cycles. Think green!
We've done enough thinking for today, let's measure!
I wrote a test program, that spends most of its time playing with smart pointers. It's not intended as a real world benchmark to measure the impact of smart pointers on performances, it's here to prove that they are not so harmless.
The benchmark has been run on the infamous 7/7 computer, with 8 threads and compiled with Visual Studio 2010 Professional Beta 1. You can get the source code here (yes I know, it doesn't do anything sensible with the data).
The results speak for themselves:
Two conclusions:
Why so much difference? Well, remember our little list, with raw pointers you're not doing anything when passing a pointer along. Going faster is just a question of doing less!
I let you imagine the overhead of having a program where "everything is smart pointed". Not only are you going to fatigue the memory allocator, but you're wasting time with counters updates when you actually really don't care about how many owners an object has!
Most of the techniques you know have their limitations and, as an engineer, your work is to make sure you use the best tool for the job. Don't be lazy in using the same technique over and over because "it works most of the time".
Smart pointers helped C++ developers to crush very nasty memory management problems, especially the typical "I have an object that moves around my program and it would be nice to free it when no one uses it anymore".
The thing is, that the more objects you have, the more expensive they become. Keep that in mind and you might be able to increase the performances of your program further!