ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


Smart Pointers in C++

by Julio M. Merino Vidal
05/04/2006

C++, with its complex and complete syntax, is a very versatile language. Because it supports object-oriented capabilities and has powerful object libraries--such as the STL or Boost--one can quickly implement robust, high-level systems. On the other hand, thanks to its C roots, C++ allows the implementation of very low-level code. This has advantages but also carries some disadvantages, especially when one attempts to write high-level applications.

In this article I describe some common pitfalls that appear when manually managing dynamic memory in C++. This leads me to analyze which possible alternatives exist to avoid them, RAII-modeled classes being a good example. And finally, I present smart pointers and a description of some popular ones.

Memory Management

One of the things that C++ inherited from C is its memory management, which C leaves completely as the developer's responsibility. C++ wraps the malloc and free functions inside the new and delete operators, which are otherwise the same. This is good for simplicity and is enough in many scenarios, but it gets in the way when implementing large and high-level applications: memory management is a delicate, tedious, and difficult task, driving the developer away from the real problem at hand. Any bugs in this code tend to be fatal, crashing the program or making it unreliable for long-term usage.

Why is it so complex? After all, C developers have done it for ages. True--but look at how many bugs originated in memory mishandling. This task also becomes more complex in C++ due to exceptions. Why is memory management so delicate in C++?

  • If a class has a dynamically allocated object as one of its attributes, developers must take care when copying those objects. The developer may opt to share the memory between objects, thus adding a reference counter, or make the instances completely stand-alone, thus needing to allocate more memory.

    In other words, what this means is that the developer must remember to write copy constructors for the affected classes. This is extra work on his side.

  • If any temporary object is part of the heap, the developer must be careful to deallocate it in all appropriate places (typically in all function exit points). Otherwise, memory will leak, making the program unreliable if it runs for long periods of time.

    As C++ supports exceptions, this task is extra difficult: every single code line can make a function exit. Consider the hypothetical code snippet:

    bool
    do_something(int size)
    {
        char* buffer;
        try {
            buffer = new char[size];
        } catch (const std::bad_alloc& e) {
            return false;
        }
    
        helper h("Something");
        h.fill(buffer, size);
    
        delete [] buffer;
        return true;
    }

    This code might look harmless, but it has a potential memory leak--the error path is not visible, but it certainly exists. If either helper's constructor or its fill method throws an exception, the function will exit without releasing buffer's memory, thus causing a leak. This kind of bug is difficult to discover. Solving it increases the code's complexity because you need extra try/catch constructions. Note that I am not implying that you shouldn't use exceptions, just saying that using them appropriately is hard.

    I recommend you read the article Cleaner, More Elegant, and Harder to Recognize, posted at The Old New Thing weblog. It illustrates this point quite well.

Of course, if you opt to manage memory manually, you can work around these problems by being extremely careful during development. (Note, though, that this extra care means more development time.) In some situations this is the only way to go, because you need full control over what the program is doing (for example, when writing low-level code).

Then there is the complete opposite solution, very well known to Java and C# developers: using a (third party) garbage collector for C++. To my knowledge, these are seldom used in C++ even when writing very high-level applications, no matter what advantages they bring. This brings up another question: why aren't garbage collectors used more often in C++ code?

  • Garbage collectors make the program's execution flow unpredictable; it depends on their implementation, though this is generally the case. Unpredictability makes the RAII model inapplicable because the developer cannot know when a class's destructor will be called--if it ever is called at all. This is a model heavily used in C++ designs.

    Just think about Java: classes can have a finalize method to do their cleanup; this method is rarely used because of its much-delayed execution. In addition to the destructor, those classes provide the developer some methods (such as close) to shut them down explicitly.

  • C++ garbage collectors are typically platform dependent. They need to be, because they deal with memory. Therefore they may reduce an application's portability.

  • Garbage collectors introduce a performance penalty. This is not a big deal (except in some notable instances) with today's computer performance. Also, this penalty might be forgiven considering the reduction in overall programming time and the easier maintenance of the code.

These three reasons, among others, may be why many developers choose not to use a garbage collector. For me, they are more than enough to look for another solution, especially because I don't like to lose portability because of third-party libraries. Yet falling back to manual control is not the best solution either, because it easily leads to the bugs described earlier.

One of the possible alternatives is to use stack (local) objects rather than heap (dynamic) objects. This is the typical and recommended C++ coding style, not to mention that many APIs enforce it: they receive and return references to local objects instead of pointers to heap objects. However, this scheme is not always applicable; multiple scenarios require the use of dynamically allocated objects.

What can you do in those cases where dynamic memory is unavoidable? A wise choice is to apply the RAII programming model to dynamically allocated objects, which brings up the well-known smart pointers.

Pages: 1, 2, 3, 4, 5

Next Pagearrow





Sponsored by: