At work I program in C++, which is a great and powerful language but also an inherently unforgiving one that imposes dire penalties for failing to adhere to its ritualized memory-management rules. Simple programs are easy to make work correctly, but anything complex runs the risk of a memory leak, which can eventually bring a system to its knees, or a double-deletion or dereferencing of a bad pointer, which can cause an instantaneous program crash.
The issue of memory management in C++, in fact, is such a big problem that there are all sorts of methodologies, class libraries, third party tools, and even alternative languages that have been developed, wholly or partially, to avoid this problem.
In my opinion, however, there is nothing easier or more effective in the vast majority of cases than using the Boost library’s (and now the TR1 library’s as well) shared_ptr<> class. shared_ptr<>, as the name implies, provides shared access to memory, which is compatible with the behavior of raw system pointers — multiple smart pointers can reference, or “own”, the target memory at one time. This means that you can easily substitute shared_ptr<> objects anywhere you would normally use a raw pointer.
However, unlike raw pointers, shared_ptr<>; objects support internal reference counting, which (except for some obscure circular reference cases) simply makes the memory management issue go away.
An example of how this might (not) work, with old-style pointers:
int main(int argc, char *argv[]) { int *i1 = new int; { int *i2 = i1; // two raw pointers now point to the same int value. delete i1; printf("%ld\n", *i2); // if above line is uncommented, we crash due to a deleted object being // accessed! If the above line is commented out, we'll leak the pointer at the // conclusion of the program. } return 0; }
With shared_ptr<>, there’s no need to use the manual “delete” operator any more — shared_ptr<> internally tracks how many total shared_ptr<> objects reference the contained object, and deletes it automatically when there are no references remaining. The code thus simplifies to:
using boost; int main(int argc, char *argv[]) { shared_ptr i1(new int); { shared_ptr i2 = i1; // two shared_ptr objects now point to the same int value. printf("%ld\n", *i2); // works fine } // at the end of this scope, the i2 object gets destroyed, but the reference // count in its destructor sees that there is still an outstanding reference, so // it's not deleted yet. return 0; } // now i1 is destroyed, the shared_ptr destructor sees that there are no // remaining references, and automatically deletes the int. No crash, no leak, // no manual memory management!
Developers like this behavior because they can drop common memory management tasks completely out of their minds and focus on more important implementation issues. Architects like it because it’s easy to retrofit into large existing codebases. Managers like it because it’s a magic bullet for stability.
So what’s the catch? Well, there are a few:
- First off, it’s a bit slower than using raw pointers.
shared_ptr<>uses an internal reference count, which makes the object larger than a corresponding raw pointer. This can cause speed issues in certain applications. However, most higher-level code is not strongly performance-sensitive, so this is usually not an issue. - Second, you can’t use the standard casting operators on them. Boost provides alternative functions to do what you need to do, but their syntax is slightly different, which increases the amount of code you need to change to retrofit them into an existing system, and is one more thing to remember when you’re working with them.
- And finally, they are verbose.
typedefwill definitely become your friend when setting upshared_ptr<>support in a system. You will likely want to set up a macro fortypedef-ing new types ofshared_ptr<>objects to cut down on the excessive keystrokes even so.
There’s a lot more to smart pointers in general, and shared_ptr<> in particular than I’ve covered here, but 99% of the time this is all you need to get rolling with them. Obviously, use C# or Java if your application calls for it, but if you are in the C++ world by force or by choice, get with the program. Folks, it’s almost 2010. If you’re still using manual memory management in cases where you aren’t certain you need to (and if you’re not certain, you probably don’t need to) you are not being smart. Let smart pointers make your code smart, and give you the time to solve real problems rather than fiddle around with memory.
This is the first in what should be a series of posts about a problem I’m addressing at work.