Smart Pointers - What, Why, Which?

来源:互联网 发布:java 代码加密 编辑:程序博客网 时间:2024/06/05 20:16

http://ootips.org/yonat/4dev/smart-pointers.html


Smart Pointers - What, Why, Which?

Yonat Sharon
  • What are they?
  • Why would I use them?
    • Less bugs
    • Exception Safety
    • Garbage collection
    • Efficiency
    • STL containers
  • Which one should I use?
    • Local variables
    • Class members
    • STL containers
    • Explicit ownership transfer
    • Big objects
    • Summary
  • Conclusion

What are they?

Smart pointers are objects that look and feel like pointers, but aresmarter. What does this mean?

To look and feel like pointers, smart pointers need tohave the same interface that pointers do: they need to support pointeroperations like dereferencing (operator *) and indirection(operator ->). An object that looks and feels like something else iscalled a proxy object, or just proxy.The proxypattern and its many uses are described in the booksDesign Patterns andPatternOriented Software Architecture.

To be smarter than regular pointers, smart pointers need to dothings that regular pointers don't. What could these things be?Probably the most common bugs in C++ (and C) are related to pointersand memory management: dangling pointers, memory leaks, allocationfailures and other joys. Having a smart pointer take care of thesethings can save a lot of aspirin...

The simplest example of a smart pointer is auto_ptr, which isincluded in the standard C++ library. You can find it in the header<memory>, or take a look atScottMeyers' auto_ptr implementation. Here is part of auto_ptr'simplementation, to illustrate what it does:

template <class T> class auto_ptr{    T* ptr;public:    explicit auto_ptr(T* p = 0) : ptr(p) {}    ~auto_ptr()                 {delete ptr;}    T& operator*()              {return *ptr;}    T* operator->()             {return ptr;}    // ...};
As you can see, auto_ptr is a simple wrapper around a regular pointer.It forwards all meaningful operations to this pointer (dereferencingand indirection). Its smartness in the destructor: the destructor takescare of deleting the pointer.

For the user of auto_ptr, this means that instead of writing:

void foo(){    MyClass* p(new MyClass);    p->DoSomething();    delete p;}
You can write:
void foo(){    auto_ptr<MyClass> p(new MyClass);    p->DoSomething();}
And trust p to cleanup after itself.

What does this buy you? See the next section.

Why would I use them?

Obviously, different smart pointers offer different reasons for use.Here are some common reasons for using smart pointers in C++.

Why: Less bugs

Automatic cleanup.As the code above illustrates, using smart pointers that clean afterthemselves can save a few lines of code. The importance here is not somuch in the keystrokes saved, but in reducing the probability for bugs:you don't need to remember to free the pointer, and so there is nochance you will forget about it.

Automatic initialization.Another nice thing is that you don't need to initialize the auto_ptr toNULL, since the default constructor does that for you. This is one lessthing for the programmer to forget.

Dangling pointers.A common pitfall of regular pointers is the dangling pointer: a pointerthat points to an object that is already deleted. The following codeillustrates this situation:

    MyClass* p(new MyClass);    MyClass* q = p;    delete p;    p->DoSomething();   // Watch out! p is now dangling!    p = NULL;           // p is no longer dangling    q->DoSomething();   // Ouch! q is still dangling!
For auto_ptr, this is solved by setting its pointer to NULL when it is copied:
template <class T>auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs){    if (this != &rhs) {        delete ptr;        ptr = rhs.ptr;        rhs.ptr = NULL;    }    return *this;}
Other smart pointers may do other things when they are copied. Here aresome possible strategies for handling the statement q = p, where p andq are smart pointers:
  • Create a new copy of the object pointed by p, and have q point to this copy. This strategy is implemented in copied_ptr.h.
  • Ownership transfer: Let both p and q point to the same object, but transfer the responsibility for cleaning up ("ownership") from p to q. This strategy is implemented in owned_ptr.h.
  • Reference counting: Maintain a count of the smart pointers that point to the same object, and delete the object when this count becomes zero. So the statement q = p causes the count of the object pointed by p to increase by one. This strategy is implemented in counted_ptr.h. Scott Meyers offers another reference counting implementation in his book More Effective C++.
  • Reference linking: The same as reference counting, only instead of a count, maintain a circular doubly linked list of all smart pointers that point to the same object. This strategy is implemented in linked_ptr.h.
  • Copy on write: Use reference counting or linking as long as the pointed object is not modified. When it is about to be modified, copy it and modify the copy. This strategy is implemented in cow_ptr.h.
All these techniques help in the battle against dangling pointers. Eachhas each own benefits and liabilities. The Which section ofthis article discusses the suitability of different smart pointersfor various situations.

Why: Exception Safety

Let's take another look at this simple example:
void foo(){    MyClass* p(new MyClass);    p->DoSomething();    delete p;}
What happens if DoSomething() throws an exception? All the lines afterit will not get executed and p will never get deleted! If we're lucky,this leads only to memory leaks. However, MyClass may free some otherresources in its destructor (file handles, threads, transactions, COMreferences, mutexes) and so not calling it my cause severe resourcelocks.

If we use a smart pointer, however, p will be cleaned up whenever itgets out of scope, whether it was during the normal path of executionor during the stack unwinding caused by throwing an exception.

But isn't it possible to write exception safe code with regularpointers? Sure, but it is so painful that I doubt anyone actually doesthis when there is an alternative. Here is what you would do in thissimple case:

void foo(){    MyClass* p;    try {        p = new MyClass;        p->DoSomething();        delete p;    }    catch (...) {        delete p;        throw;    }}
Now imagine what would happen if we had some if's and for'sin there...

Why: Garbage collection

Since C++ does not provide automatic garbage collection like some otherlanguages, smart pointers can be used for that purpose. The simplestgarbage collection scheme is reference counting or reference linking,but it is quite possible to implement more sophisticated garbagecollection schemes with smart pointers. For more information seethe garbage collectionFAQ.

Why: Efficiency

Smart pointers can be used to make more efficient use of availablememory and to shorten allocation and deallocation time.

A common strategy for using memory more efficiently is copy on write(COW). This means that the same object is shared by many COW pointersas long as it is only read and not modified. When some part of theprogram tries to modify the object ("write"), the COW pointercreates a new copy of the object and modifies this copy instead of theoriginal object. The standard string class is commonly implementedusing COW semantics (see the <string> header).

    string s("Hello");    string t = s;       // t and s point to the same buffer of characters    t += " there!";     // a new buffer is allocated for t before                        // appending " there!", so s is unchanged.

Optimized allocation schemes are possible when you can make someassumptions about the objects to be allocated or the operatingenvironment. For example, you may know that all the objects will havethe same size, or that they will all live in a single thread. Althoughit is possible to implement optimized allocation schemes usingclass-specific new and delete operators, smart pointers give you thefreedom to choose whether to use the optimized scheme for each object,instead of having the scheme set for all objects of a class. It istherefore possible to match the allocation scheme to differentoperating environments and applications, without modifying the code forthe entire class.

Why: STL containers

The C++ standard library includes a set of containers and algorithmsknown as the standard template library (STL).STL isdesigned to be generic (can be used with any kind of object)and efficient (does not incur time overhead compared toalternatives). To achieve these two design goals, STL containers storetheir objects by value. This means that if you have an STL containerthat stores objects of class Base, it cannot store of objects ofclasses derived from Base.
    class Base { /*...*/ };    class Derived : public Base { /*...*/ };    Base b;    Derived d;    vector<Base> v;    v.push_back(b); // OK    v.push_back(d); // error
What can you do if you need a collection of objects from differentclasses? The simplest solution is to have a collection of pointers:
vector<Base*> v;v.push_back(new Base);      // OKv.push_back(new Derived);   // OK too// cleanup:for (vector<Base*>::iterator i = v.begin(); i != v.end(); ++i)    delete *i;

The problem with this solution is that after you're done with thecontainer, you need to manually cleanup the objects stored in it. Thisis both error prone and not exception safe.

Smart pointers are a possible solution, as illustrated below. (Analternative solution is a smart container, like the one implemented inpointainer.h.)

    vector< linked_ptr<Base> > v;    v.push_back(new Base);      // OK    v.push_back(new Derived);   // OK too    // cleanup is automatic
Since the smart pointer automatically cleans up after itself, there isno need to manually delete the pointed objects.

Note: STL containers may copy and delete their elements behind thescenes (for example, when they resize themselves). Therefore, allcopies of an element must be equivalent, or the wrong copy may be theone to survive all this copying and deleting. This means that somesmart pointers cannot be used within STL containers, specifically thestandard auto_ptr and any ownership-transferring pointer. For more infoabout this issue, seeC++ Guru of theWeek #25.

Which one should I use?

Are you confused enough? Well, this summary should help.

Which: Local variables

The standard auto_ptr is the simplest smart pointer, and it is also,well, standard. If there are no special requirements, you should useit. For local variables, it is usually the right choice.

Which: Class members

Although you can use auto_ptr as a class member (and save yourself thetrouble of freeing objects in the destructor), copying one object toanother will nullify the pointer, as illustrated Below.
    class MyClass    {        auto_ptr<int> p;        // ...    };    MyClass x;    // do some meaningful things with x    MyClass y = x; // x.p now has a NULL pointer
Using a copied pointer instead of auto_ptr solves this problem: thecopied object (y) gets a new copy of the member.

Note that using a reference counted or reference linked pointermeans that if y changes the member, this change will also affect x!Therefore, if you want to save memory, you should use a COW pointer andnot a simple reference counted/linked pointer.

Which: STL containers

As explained above, using garbage-collected pointers with STLcontainers lets you store objects from different classes in the samecontainer.

It is important to consider the characteristics of the specificgarbage collection scheme used. Specifically, referencecounting/linking can leak in the case of circular references (i.e.,when the pointed object itself contains a counted pointer, which pointsto an object that contains the original counted pointer). Itsadvantage over other schemes is that it is both simple to implement anddeterministic. The deterministic behavior may be important in some realtime systems, where you cannot allow the system to suddenly wait whilethe garbage collector performs its housekeeping duties.

Generally speaking, there are two ways to implement referencecounting: intrusive and non-intrusive. Intrusive means that the pointedobject itself contains the count. Therefore, you cannot use intrusivereference counting with 3-rd party classes that do not already havethis feature. You can, however, derive a new class from the 3-rd partyclass and add the count to it. Non-intrusive reference countingrequires an allocation of a count for each counted object. Thecounted_ptr.h is an example ofnon-intrusive reference counting.

  Reference linking does not require any changes to be made to thepointed objects, nor does it require any additional allocations. Areference linked pointer takes a little more space than a referencecounted pointer - just enough to store one or two more pointers. 

Both reference counting and reference linking require using locks ifthe pointers are used by more than one thread of execution.

Which: Explicit ownership transfer

Sometimes, you want to receive a pointer as a function argument, butkeep the ownership of this pointer (i.e. the control over its lifetime)to yourself. One way to do this is to use consistent naming-conventionsfor such cases.Taligent's Guide to Designing Programs recommends using"adopt" to mark that a function adopts ownership of a pointer.

Using an owned pointer as the function argument is an explicitstatement that the function is taking ownership of the pointer.

Which: Big objects

If you have objects that take a lot of space, you can save some of thisspace by using COW pointers. This way, an object will be copied onlywhen necessary, and shared otherwise. The sharing is implemented usingsome garbage collection scheme, like reference counting or linking.

Which: Summary

For this:Use that:Local variablesauto_ptrClass membersCopied pointerSTL ContainersGarbage collected pointer (e.g. reference counting/linking)Explicit ownership transferOwned pointerBig objectsCopy on write

Conclusion

Smart pointers are useful tools for writing safe and efficient code inC++. Like any tool, they should be used with appropriate care, thoughtand knowledge. For a comprehensive and in depth analysis of the issues concerningsmart pointers, I recommend reading Andrei Alexandrescu'schapter about smart pointersin his bookModern C++ Design.

Feel free to use myown smart pointers in your code.
The Boost C++ libraries include somesmart pointers, which are more rigorously tested and actively maintained.Do try them first, if they are appropriate for your needs.


原创粉丝点击