- Overview
-
Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
- Namespaces
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
- Tips and Techniques
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
-
C++0X: The New Face of Standard C++
- Reference Wrapper
- The Performance Technical Report
- auto for the People
- Ironing Templates' Syntactic Wrinkles
- Visual C++ Becomes ISO Compliant
- A Garbage Collector for C++
- C99 Core Features in C++0X
- The <code>shared_ptr</code> Class
- The shared_ptr Class, II
- Lambda Expressions and Closures, Part I
- Lambda Expressions and Closures, Part II
- Lambda Expressions and Closures, Part III
- The Type Traits Library, Part I
- The Type Traits Library, Part II
- The Type Traits Library, Part III
- finally Revisited
- The Any Library
- The nullptr Keyword Proposal
- Delegating Constructors
- The Explicit Conversion Operators Proposal
- Conditionally-Supported Behavior
- The weak ptr Class Template, Part I
- The weak ptr Class Template, Part II
- POD Types Revisited
- The rvalue Reference Proposal, Part I
- The rvalue Reference Proposal, Part II
- Proposal for New String Algorithms
- Concepts, Part I
- Concepts, Part II
- constexpr: Generalized Constant Expressions
- The <u>constexpr</u> Proposal: Constructors
- Strongly-Typed enum Types
- C++09: The Road Ahead
- C++09: Proposals by Statuses
- Changing Undefined Behavior to Diagnosable Errors
- New Character Types
- The __func__ Predeclared Identifier is Coming to C++
- Static Assertions
- The extern template Proposal
- Variadic Templates, Part I
- Variadic Templates, Part II
- Variadic Templates, Part III -- Critique
- Using unique_ptr, Part I
- Using unique_ptr, Part II
- Unrestricted Unions, Part I
- Unrestricted Unions, Part II
- Unrestricted Unions, Part III
- Types With No Linkage as Template Arguments
- New Initialization Syntax
- Initializer Lists and Sequence Constructors
- New Standard Library Algorithms
- Class Member Initializers
- Inheriting Constructors
- Introducing Attributes
- The Removal of Concepts From C++0x
- The Future of C++0x, Part I
- The Future of C++0X, Part II
- The Debate About Attributes, Part I
- The Debate About Attributes, Part II
- The Debate About Attributes, Part III
- The Debate About Attributes, Part IV
- Forward Declarations of Enum Types
- The SCARY Iterators Proposal, Part I
- The SCARY Iterators Proposal, Part II
- Heading for Deprecation: <tt>export</tt>, Exception Specification and <tt>register</tt>
- The Rejection of the Unified Function Syntax Proposal
- Rvalue References as Object Members
- FCD Approved
- The Debate on noexcept, Part I
- The Debate on noexcept, Part II
- The Debate on noexcept, Part III
- About-face -- [[Attributes]] to Be Replaced with Keywords
- Will Delegating Constructors Be Removed From C++0x?
- Rvalue References: Past, Present and Future, Part I
- Rvalue References: Past, Present and Future, Part II
- Rvalue References: Past, Present and Future, Part III
- A Move in the Right Direction, Part I
- A Move in the Right Direction, Part II
- New Keywords for Inheritance Control, Part I
- New Keywords for Inheritance Control, Part II
- FDIS Approved
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
The <code>shared_ptr</code> Class
Last updated Jan 1, 2003.
C++98 has only one smart pointer class, namely auto_ptr. The Library Extensions Technical Report 1 (known as TR1) introduces new smart pointer classes that rectify many of auto_ptr's problems and limitations. Here, I present the most widely-used smart pointer class of TR1, namely shared_ptr, and contrast it with good old auto_ptr.
auto_ptr's Limitations
auto_ptr employs the "exclusive ownership" model. This means that you can't bind more than one auto_ptr object to the same resource. To ensure exclusive ownership, the copy and assign operations of auto_ptr cause the source object to hand down the owned resource to the target object. Let's see an example:
void f()
{
std::auto_ptr<Foo> pfoo1(new Foo);
pfoo1->func(); //use pfoo1 as if it were the Foo object
//copying changes the source and the target
std::auto_ptr<Foo> pfoo2(pfoo1); //now pf002 owns the pointer to Foo
//pfoo1 becomes null
pfoo2->func();
} //pfoo's destructor deletes the Foo pointer
There are at least two problems with this ownership model.
- Counter-intuitive copy semantics. Generally, when you copy a source to a target, you don't expect the source to change its state. When you assign a string s1 to another string s2, you surely don't expect s1 to change. It's worth mentioning that copying occurs more frequently than what may seem at first: returning and passing objects by value are instances of copying, too.
- Incompatibility with STL. You can't use auto_ptr as elements of STL containers. The reason is that containers may occasionally move elements around (say, during reallocation or when you assign containers). The elements must therefore be copy-constructible and assignable; auto_ptr is neither of these.
Why was this design of auto_ptr chosen in the first place? Let's look at the previous code listing again to see why. If multiple auto_ptrs were allowed to hold the same pointer, the results would be disastrous. When f() exits, the destructors of the two auto_ptrs would delete the same pointer twice:
void f()
{
std::auto_ptr<Foo> pfoo1(new Foo);
pfoo1->func();
//imagine that copying doesn't change the source
std::auto_ptr<Foo> pfoo2(pfoo1);
// pfoo1and pfoo2 own the same object
pfoo2->func();
} //both pfoo1 and pffo2's destructors delete the Foo pointer
Now let's see how shared_ptr solves this problem neatly.
Enter shared_ptrs
As with many other TR1 components, the first implementation of shared_ptr was available on Boost. The input from a vast number of users and gurus has enabled its designers to fine-tune the interface and performance of this class. Only then was shared_ptr incorporated into TR1.
The result is a robust, flexible and rather efficient smart pointer class that rectifies the ailments of auto_ptr, while extending the potential usage of shared_ptr to diverse applications domains and frameworks, such as COM.
The interface of shared_ptr is pretty reminiscent of auto_ptr's, which is just as well; the learning curve isn't steep, and the migration of auto_ptr-based code to shared_ptr isn't difficult, either.
However, there is a crucial difference between the two. Unlike auto_ptr, shared_ptr uses reference counting. Consequently, it's possible to have multiple shared_ptrs that point to the same resource at once. These objects use a reference counter. When a shared_ptr's destructor is invoked, the reference counter is decremented. Only when the reference counter reaches 0 does the shared_ptr destructor release the resource. This design has two advantages: you can safely store shared_ptr objects as elements of STL containers, and you can safely create multiple shared_ptrs that point to the same resource. In multithreaded apps, this enables you, for instance, to create a shared_ptr object per thread, and execute different member functions of the same resource object concurrently:
Foo * p = new Foo;
{//thread 1; executes concurrently with thread 2
std::tr1::shared_ptr<Foo> pfoo1(p);
pfoo1->func();
return 0;
}
{//thread 2; executes concurrently with thread 1
std::tr1::shared_ptr<Foo> pfoo2(p);
pfoo1->another_func();
return 0;
}
It doesn't matter which thread finishes first; in any occasion, shared_ptr "just works" -- the last instance thereof will delete p;
As all smart pointer classes, shared_ptr overloads the -> and * operators so you normally don't need to access its raw pointer directly. However, if you need to access the pointer directly, call the get() member function:
Foo * p_alias = std::tr1::pfoo1.get(); assert(p_alias==p);
Construction
The shared_ptr constructor takes two optional arguments. The first is obviously a pointer to the resource object, as I've shown before. Notice that the constructor is templated on the argument's type. This allows you to do the following:
std::tr1:shared_ptr <void> pf(new Foo);
When pf is destroyed, it invokes Foo's destructor, as expected.
The second optional argument is even more interesting. It specifies a user-defined deleter, which is a function (in its wider sense, e.g., a function object, a pointer to a freestanding function etc.) that the shared_ptr destructor executes when the reference count reaches zero. By default, shared_ptr's destructor simply calls delete to destroy its resource. However, when you provide a custom deleter, shared_ptr's destructor invokes that deleter instead. This is useful when the resource in question isn't a raw pointer to dynamic memory, or when the dynamic memory belongs to a different heap (and therefore shouldn't be deleted, say a pointer to an object allocated in a different DLL):
//API functions DeviceContext * get_dc(); int release_dc(); void draw() //provide a user-defined deleter std::tr1::shared_ptr<DeviceContext> dc(get_dc(), release_dc); //...use dc //instead of deleting the dc pointer, the release_dc() API function //will be called when dc is destroyed. }
It's also possible to construct a null shared_ptr, possibly binding a valid pointer to it later:
std::tr1::shared_ptr<Bar> pb;
use_count() and unique()
The use_count() member function returns the number of clients (i.e., shared_ptr objects) currently sharing the same resource. The common
use_count() == 1
query has its own name: unique():
std::tr1::shared_ptr<DeviceContext> dc(get_dc(), release_dc);
if(dc.unique())
{
//...resource isn't shared with others
}
The shared_ptr class also defines a bool conversion operator, enabling you to use it in Boolean expressions.
The relative operators ==, != and < are also defined. They do not compare raw pointers but rather use the std::less predicate to perform the comparison.
In the next installment, I will show how to use shared_ptr to simulate a heterogeneous container and discuss the related class weak_ptr, which is also part of TR1.
