Wednesday, October 14, 2009

21.8. Smart Pointers with Boost.Smart_ptr



21.8. Smart Pointers with
Boost.Smart_ptr


Many common bugs in C and C++ code
are related to pointers. Smart pointers help you avoid errors by providing additional
functionality to standard pointers. This functionality typically strengthens the
process of memory allocation and deallocation. Smart pointers also help you
write exception safe code. If a program throws an exception before
delete has been called on a pointer, it creates a
memory leak. After an exception is thrown, a smart pointer's destructor will
still be called, which calls delete on the
pointer for you.


The C++ Standard Library provides a
basic smart pointer, std::auto_ptr. An auto_ptr is responsible
for managing dynamically allocated memory and automatically calls
delete to free the dynamic memory when the auto_ptr is destroyed or goes out of scope. These smart pointers
are perfect for automatic variables. Section
16.12 discusses auto_ptr.


auto_ptrs have some limitations. For example, an
auto_ptr can't point to an array. When deleting a
pointer to an array you must use delete[] to
ensure that destructors are called for all objects in the array, but
auto_ptr uses delete. Another limitation of
auto_ptr is that it can't be used with the
STL containers—elements in an STL container must be able to be safely copied.
When an auto_ptr is copied, ownership of the
memory is transferred to the new auto_ptr and the
original is set to NULL. An STL container may
make copies of its elements, so you can't guarantee that a valid copy of the
auto_ptr will remain after the algorithm
processing the container's elements finishes.


The Boost.Smart_ptr
library provides additional smart pointers to fill in the gaps where
auto_ptrs don't work. TR1 includes two of the six types
of smart pointers in the Boost.Smart_ptr library, namely
shared_ptr and weak_ptr. These smart
pointers are not meant to replace auto_ptr. Instead, they provide additional options with
different functionality.


21.8.1. Reference Counted
shared_ptr


shared_ptrs hold an internal
pointer to a resource (e.g., a dynamically allocated object) that may be shared
with other objects in the program. You can have any number of
shared_ptrs to the same resource.
shared_ptrs really do share the resource—if
you change the resource with one shared_ptr, the
changes also will be "seen" by the other shared_ptrs. The internal pointer is deleted once the last
shared_ptr to the resource is destroyed.
shared_ptrs use reference counting
to determine how many shared_ptrs point to the resource. Each time a
new shared_ptr to the resource is created, the reference count
increases, and each time one is destroyed, the reference count decreases. When
the reference count reaches zero, the internal pointer is deleted and the memory
is released.


shared_ptrs are useful in
situations where multiple pointers to the same resource are needed, such as in
STL containers. auto_ptrs don't work in STL
containers because the containers, or algorithms manipulating them, might copy
the stored elements. Copies of auto_ptrs aren't equal because the
original is set to NULL after being copied. shared_ptrs, on the other hand, can safely be copied and used in
STL containers.


shared_ptrs also allow you to
determine how the resource will be destroyed. For most dynamically allocated
objects, delete is used. However, some
resources require more complex cleanup. In that case, you can supply a custom
deleter function, or
function object, to the shared_ptr
constructor. The deleter determines how to destroy the resource. When the
reference count reaches zero and the resource is ready to be destroyed, the shared_ptr calls the custom deleter function. This
functionality enables a shared_ptr to manage almost any kind of
resource.


Example Using shared_ptr

Figures
21.6–21.7 define
a simple class to represent a Book with a string to represent
the title of the Book. The destructor for class Book (Fig. 21.7, lines 16–19) displays a message on the screen
indicating that an instance is being destroyed. We use this class to demonstrate
the common functionality of shared_ptr.











Fig. 21.6. Book header file.

 1   // Fig. 21.6: Book.h
2 // Declaration of class Book.
3 #ifndef BOOK_H
4 #define BOOK_H
5 #include <string>
6 using std::string;
7
8 class Book
9 {
10 public:
11 Book( const string &bookTitle ); // constructor
12 ~Book(); // destructor
13 string title; // title of the Book
14 };
15 #endif // BOOK_H












Fig. 21.7. Book member-function
definitions.

 1   // Fig. 21.7: Book.cpp
2 // Member-function definitions for class Book.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <string>
8 using std::string;
9
10 #include "Book.h"
11
12 Book::Book( const string &bookTitle ) : title( bookTitle )
13 {
14 }
15
16 Book::~Book()
17 {
18 cout << "Destroying Book: " << title << endl;
19 } // end of destructor



The program in Fig. 21.8 uses shared_ptrs
to manage several instances of class Book. We include the
"boost/shared_ptr.hpp" header file to be able to use
shared_ptrs. We also create
a typedef, BookPtr, as an alias for the type boost::shared_ptr<
Book >
(line 13). Line 31 creates a shared_ptr to a
Book titled "C++ How to Program" (using the BookPtr
typedef
). The shared_ptr constructor takes as
its argument a pointer to an object. We pass it the pointer returned from the
new operator. This creates a shared_ptr that manages the Book object and sets the reference count
to one. The constructor can also take another shared_ptr, in which case it shares ownership of the resource with
the other shared_ptr and the reference count is
increased by one. The first shared_ptr to a
resource should always be created using the new operator. A
shared_ptr created with a regular pointer
assumes it's the first shared_ptr assigned
to that resource and starts the reference count at one. If you make multiple
shared_ptrs with the same pointer, the
shared_ptrs won't acknowledge each other and the
reference count will be wrong. When the shared_ptrs are destroyed, they
both call delete on the resource.













Fig. 21.8. shared_ptr example
program.


 

 1   // Fig. 21.8: fig21_8.cpp
2 // Demonstrate used of shared_ptrs.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <vector>
8 using std::vector;
9
10 #include "Book.h"
11 #include "boost/shared_ptr.hpp"
12
13 typedef boost::shared_ptr< Book > BookPtr; // shared_ptr to a Book
14
15 // a custom delete function for a pointer to a Book
16 void deleteBook( Book* book )
17 {
18 cout << "Custom deleter for a Book, ";
19 delete book; // delete the Book pointer
20 } // end of deleteBook
21
22 // compare the titles of two Books for sorting
23 bool compareTitles( BookPtr bookPtr1, BookPtr bookPtr2 )
24 {
25 return ( bookPtr1->title <bookPtr2->title );
26 } // end of compareTitles
27
28 int main()
29 {
30 // create a shared_ptr to a Book and display the reference count
31 BookPtr bookPtr( new Book( "C++ How to Program" ) );
32 cout << "Reference count for Book " << bookPtr->title << " is: "
33 << bookPtr.use_count() << endl;
34
35 // create another shared_ptr to the Book and display reference count
36 BookPtr bookPtr2( bookPtr );
37 cout << "Reference count for Book " << bookPtr->title << " is: "
38 << bookPtr.use_count() << endl;
39
40 // change the Book's title and access it from both pointers
41 bookPtr2->title = "Java How to Program";
42 cout << "The Book's title changed for both pointers: "
43 << "\nbookPtr: " << bookPtr->title
44 << "\nbookPtr2: " << bookPtr2->title <<endl;
45
46 // create a std::vector of shared_ptrs to Books (BookPtrs)
47 vector< BookPtr > books;
48 books.push_back( BookPtr( new Book( "C How to Program" ) ) );
49 books.push_back( BookPtr( new Book( "VB How to Program" ) ) );
50 books.push_back( BookPtr( new Book( "C# How to Program" ) ) );
51 books.push_back( BookPtr( new Book( "C++ How to Program" ) ) );
52
53 // print the Books in the vector
54 cout << "\nBooks before sorting: " << endl;
55 for ( int i = 0; i < books.size(); i++ )
56 cout << ( books[ i ] )->title << "\n";
57
58 // sort the vector by Book title and print the sorted vector
59 sort( books.begin(), books.end(), compareTitles );
60 cout << "\nBooks after sorting: " << endl;
61 for ( int i = 0; i < books.size(); i++ )
62 cout << ( books[ i ] )->title << "\n";
63
64 // create a shared_ptr with a custom deleter
65 cout << "\nshared_ptr with a custom deleter." << endl;
66 BookPtr bookPtr3( new Book( "Small C++ How to Program" ), deleteBook);
67 bookPtr3.reset(); // release the Book this shared_ptr manages
68
69 // shared_ptrs are going out of scope
70 cout << "\nAll shared_ptr objects are going out of scope." << endl;
71
72 return 0;
73 } // end of main




 

Reference count for Book C++ How to Program is: 1
Reference count for Book C++ How to Program is: 2

The Book's title changed for both pointers:
bookPtr: Java How to Program
bookPtr2: Java How to Program

Books before sorting:
C How to Program
VB How to Program
C# How to Program
C++ How to Program

Books after sorting:
C How to Program
C# How to Program
C++ How to Program
VB How to Program

shared_ptr with a custom deleter.
Custom deleter for a Book, Destroying Book: Small C++ How to Program

All shared_ptr objects are going out of scope.
Destroying Book: C How to Program
Destroying Book: C# How to Program
Destroying Book: C++ How to Program
Destroying Book: VB How to Program
Destroying Book: Java How to Program




Lines 32–33 display the Book's title and the
number of shared_ptrs referencing that instance. Notice that we use the
-> operator to access the Book's data member title, as we
would with a regular pointer. shared_ptrs provide the pointer operators
* and ->. We get the reference count using the
shared_ptr member function use_count, which returns the number of
shared_ptrs to the resource. Then we create another shared_ptr
to the instance of class Book (line 36). Here we use use the
shared_ptr constructor with the original shared_ptr as its argument. You can also use the assignment
operator (=) to create a shared_ptr to the same
resource. Lines 37–38 print the reference count of the original
shared_ptr to show that the count increased
by one when we created the second shared_ptr. As mentioned earlier, changes made
to the resource of a shared_ptr are "seen" by all
shared_ptrs to that resource. When we change
the title of the Book using bookPtr 2
(line 41), we can see the change when using bookPtr (lines 42–44).


Next we demonstrate using
shared_ptrs in an STL container. We create a
vector of BookPtrs (line 47) and add four elements (recall
that BookPtr is a typedef for a shared_ptr< Book
>
, line 13). This demonstrates a key advantage of using
shared_ptr instead of auto_ptr—as mentioned earlier, you can't
use auto_ptrs in STL containers. Lines 54–56 print the contents of the
vector. Then we sort the Books in the vector by title
(line 59). We use the function compareTitles (lines 23–26) in the
sort algorithm to compare the title data members of each
Book alphabetically.


Line 66 creates a shared_ptr with a custom deleter. We
define the custom deleter function deleteBook (lines 16–20) and pass it
to the shared_ptr constructor along with a pointer to
a new instance of class Book. When the shared_ptr destroys the
instance of class Book, it calls deleteBook with the internal
Book * as the argument. Notice that deleteBook takes a
Book *, not a shared_ptr. A custom
deleter function must take one argument of the shared_ptr's internal
pointer type. deleteBook displays a message to
show that the custom deleter was called, then deletes the pointer. We call the
shared_ptr member function reset (line 67) to show the custom deleter
at work. The reset function
releases the current resource and sets the shared_ptr to NULL.
If there are no other shared_ptrs to the resource, it's destroyed. You
can also pass a pointer or shared_ptr representing a
new resource to the reset function, in which case the shared_ptr will manage the new resource. But, as with the
constructor, you should only use a regular pointer returned by the new
operator.


All the shared_ptrs and the vector go out of scope at the end of the main function and are destroyed. When the vector is destroyed, so are the shared_ptrs in it. The program output shows that each instance of
class Book is destroyed automatically by the shared_ptrs.
There is no need to delete each pointer placed in the
vector.


21.8.2. weak_ptr:
shared_ptr
Observer


A weak_ptr points to the resource managed by a
shared_ptr without assuming any responsibility for it. The reference
count for a shared_ptr doesn't increase when a weak_ptr
references it. That means that the resource of a shared_ptr can be
deleted while there are still weak_ptrs pointing to it. When the last
shared_ptr is destroyed, the resource is deleted and any remaining
weak_ptrs are set to NULL. One use for weak_ptrs, as we'll demonstrate later in this section, is to avoid
memory leaks caused by circular references.


A weak_ptr can't directly
access the resource it points to—you must create a shared_ptr from the
weak_ptr to access the resource. There are two
ways to do this. You can pass the weak_ptr to the shared_ptr
constructor. That creates a shared_ptr to the resource being pointed to
by the weak_ptr and properly increases the
reference count. If the resource has already been deleted, the
shared_ptr constructor will throw a
boost::bad_weak_ptr exception. You can also call the weak_ptr member
function lock, which returns a
shared_ptr to the weak_ptr's resource. If the
weak_ptr points to a deleted resource
(i.e., NULL), lock will return an empty
shared_ptr (i.e., a shared_ptr to NULL).
lock should be used when an empty shared_ptrisn't
considered an error. You can access the resource once you have a
shared_ptr to it. weak_ptrs should
be used in any situation where you need to observe the resource but don't want
to assume any management responsibilities for it. The following example
demonstrates the use of weak_ptrs in circularly
referential data,
a situation in which two objects
refer to each other internally.


Example Using weak_ptr

Figures
21.9–21.12
define classes Author and Book. Each
class has a pointer to an instance of the other class. This creates a circular
reference between the two classes. Note that we use both weak_ptrs and
shared_ptrs to hold the cross reference to each class (Fig. 21.9 and 21.10, lines 21–22 in each figure).
If we set the shared_ptrs, it creates a memory
leak—we'll explain why soon and show how we can use the weak_ptrs to fix this problem.











Fig. 21.9. Author class
definition.


 

 1   // Fig. 21.9: Author.h
2 // Definition of class Author.
3 #ifndef AUTHOR_H
4 #define AUTHOR_H
5 #include <string>
6 using std::string;
7
8 #include "boost/shared_ptr.hpp"
9 #include "boost/weak_ptr.hpp"
10
11 class Book; // forward declaration of class Book
12
13 // Author class definition
14 class Author
15 {
16 public:
17 Author( const string &authorName ); // constructor
18 ~Author(); // destructor
19 void printBookTitle(); // print the title of the Book
20 string name; // name of the Author
21 boost::weak_ptr< Book > weakBookPtr; // Book the Author wrote
22 boost::shared_ptr< Book > sharedBookPtr; // Book the Author wrote
23 };
24 #endif // AUTHOR_H













Fig. 21.10. Book class
definition.


 

 1   // Fig. 21.10: Book.h
2 // Definition of class Book.
3 #ifndef BOOK_H
4 #define BOOK_H
5 #include <string>
6 using std::string;
7
8 #include "boost/shared_ptr.hpp"
9 #include "boost/weak_ptr.hpp"
10
11 class Author; // forward declaration of class Author
12
13 // Book class definition
14 class Book
15 {
16 public:
17 Book( const string &bookTitle ); // constructor
18 ~Book(); // destructor
19 void printAuthorName(); // print the name of the Author
20 string title; // title of the Book
21 boost::weak_ptr< Author > weakAuthorPtr; // Author of the Book
22 boost::shared_ptr< Author > sharedAuthorPtr; // Author of the Book
23 };
24 #endif // BOOK_H




Classes Author and
Book define destructors that each display a
message to indicate when an instance of either class is destroyed (Figs. 21.11 and 21.12, lines 19–22). Each class also defines a member
function to print the title of the Book and Author's
name (lines 25–38 in each figure). Recall
that you can't access the resource directly through a weak_ptr, so first we create a shared_ptr from the
weak_ptr data member (line 28 in each figure). If the resource the
weak_ptr is referencing doesn't exist, the call to the lock
function returns a shared_ptr which points to NULL and the
condition fails. Otherwise, the new shared_ptr contains a valid pointer
to the weak_ptr's resource, and we can access the
resource. If the condition in line 28 is true (i.e., bookPtr and
authorPtr aren't NULL), we print the
reference count to show that it increased with the creation of the new
shared_ptr, then we print the title of the Book and
Author's name. The shared_ptr is
destroyed when the function exits so the reference count decreases by one.












Fig. 21.11. Author member-function
definitions.


 

 1   // Fig. 21.11: Author.cpp
2 // Member-function definitions for class Author.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <string>
8 using std::string;
9
10 #include "Author.h"
11 #include "Book.h"
12 #include "boost/shared_ptr.hpp"
13 #include "boost/weak_ptr.hpp"
14
15 Author::Author( const string &authorName ) : name( authorName )
16 {
17 }
18
19 Author::~Author()
20 {
21 cout << "Destroying Author: " << name << endl;
22 } // end of destructor
23
24 // print the title of the Book this Author wrote
25 void Author::printBookTitle()
26 {
27 // if weakBookPtr.lock() returns a non-empty shared_ptr
28 if ( boost::shared_ptr< Book > bookPtr = weakBookPtr.lock() )
29 {
30 // show the reference count increase and print the Book's title
31 cout << "Reference count for Book " << bookPtr->title
32 << " is " << bookPtr.use_count() << "." << endl;
33 cout << "Author " << name << " wrote the book " << bookPtr->title
34 << "\n" << endl;
35 } // end if
36 else // weakBookPtr points to NULL
37 cout << "This Author has no Book." << endl;
38 } // end of printBookTitle













Fig. 21.12. Book member-function
definitions.


 

 1   // Fig. 21.12: Book.cpp
2 // Member-function definitions for class Book.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <string>
8 using std::string;
9
10 #include "Author.h"
11 #include "Book.h"
12 #include "boost/shared_ptr.hpp"
13 #include "boost/weak_ptr.hpp"
14
15 Book::Book( const string &bookTitle ) : title( bookTitle )
16 {
17 }
18
19 Book::~Book()
20 {
21 cout << "Destroying Book: " << title << endl;
22 } // end of destructor
23
24 // print the name of this Book's Author
25 void Book::printAuthorName()
26 {
27 // if weakAuthorPtr.lock() returns a non-empty shared_ptr
28 if ( boost::shared_ptr< Author > authorPtr = weakAuthorPtr.lock() )
29 {
30 // show the reference count increase and print the Author's name
31 cout << "Reference count for Author " << authorPtr->name
32 << " is " << authorPtr.use_count() << "." << endl;
33 cout << "The book " << title << " was written by "
34 << authorPtr->name << "\n" << endl;
35 } // end if
36 else // weakAuthorPtr points to NULL
37 cout << "This Book has no Author." << endl;
38 } // end of printAuthorName




Figure 21.13 defines a
main function that demonstrates the
memory leak caused by the circular reference between classes Author and Book. Lines 14–16 create shared_ptrs to an instance of each class. The weak_ptr data members are set in lines 19–20. Lines 23–24 set the
shared_ptr data members for each class. The
instances of classes Author and Book now
reference each other. We then print the reference count for the
shared_ptrs to show that each instance is referenced by two
shared_ptrs (lines 27–30), the ones we create in the
main function and the data member of each instance. Remember that
weak_ptrs don't affect the reference count.
Then we call each class's member function to print the information stored in the
weak_ptr data member (lines 35–36). The
functions also display the fact that another shared_ptr was created during the function call. Finally, we
print the reference counts again to show that the additional
shared_ptrs created in the printAuthorName and
printBookTitle member functions are destroyed when the functions
finish.













Fig. 21.13. shared_ptrs cause a memory leak in
circularly referential data


 

 1   // Fig. 21.13: fig21_13.cpp
2 // Demonstrate use of weak_ptr.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include "Author.h"
8 #include "Book.h"
9 #include "boost/shared_ptr.hpp"
10
11 int main()
12 {
13 // create a Book and an Author
14 boost::shared_ptr< Book > bookPtr( new Book( "C++ How to Program" ) );
15 boost::shared_ptr< Author > authorPtr(
16 new Author( "Deitel & Deitel" ) );
17
18 // reference the Book and Author to each other
19 bookPtr->weakAuthorPtr = authorPtr;
20 authorPtr->weakBookPtr = bookPtr;
21
22 // set the shared_ptr data members to create the memory leak
23 bookPtr->sharedAuthorPtr = authorPtr;
24 authorPtr->sharedBookPtr = bookPtr;
25
26 // reference count for bookPtr and authorPtr is one
27 cout << "Reference count for Book " << bookPtr->title << " is "
28 << bookPtr.use_count() << endl;
29 cout << "Reference count for Author " << authorPtr->name << " is "
30 << authorPtr.use_count() << "\n" << endl;
31
32 // access the cross references to print the data they point to
33 cout << "\nAccess the Author's name and the Book's title through "
34 << "weak_ptrs." << endl;
35 bookPtr->printAuthorName();
36 authorPtr->printBookTitle();
37
38 // reference count for each shared_ptr is back to one
39 cout << "Reference count for Book " << bookPtr->title << " is "
40 << bookPtr.use_count() << endl;
41 cout << "Reference count for Author " << authorPtr->name << " is "
42 << authorPtr.use_count() << "\n" << endl;
43
44 // the shared_ptrs go out of scope, the Book and Author are destroyed
45 cout << "The shared_ptrs are going out of scope." << endl;
46
47 return 0;
48 } // end of main



Reference count for Book C++ How to Program is 2
Reference count for Author Deitel & Deitel is 2

Access the Author's name and the Book's title through weak_ptrs.
Reference count for Author Deitel & Deitel is 3.
The book C++ How to Program was written by Deitel & Deitel

Reference count for Book C++ How to Program is 3.
Author Deitel & Deitel wrote the book C++ How to Program

Reference count for Book C++ How to Program is 2
Reference count for Author Deitel & Deitel is 2

The shared_ptrs are going out of scope.



At the end of main, the shared_ptrs to the
instances of Author and Book we
created go out of scope and are destroyed. Notice that the output doesn't show
the destructors for classes Author and Book. The program has a memory leak—the instances of
Author and Book aren't destroyed because of
the shared_ptr data members. When bookPtr
is destroyed at the end of the main function, the
reference count for the instance of class Book becomes one—the instance
of Author still has a shared_ptr to the instance of
Book, so it's not deleted. When authorPtr goes out of scope and is destroyed, the reference count
for the instance of class Author also becomes one—the instance of
Book still has a shared_ptr to the instance of
Author. Neither instance is deleted because the
reference count for each is still one.


Now, comment out lines 23–24 by placing
// at the beginning of each line. This
prevents the code from setting the shared_ptr data members for classes Author and
Book. Recompile the code and run the program
again. Figure
21.14 shows the output. Notice that the initial
reference count for each instance is now one instead of two because we don't set
the shared_ptr data members. The last two lines
of the output show that the instances of classes Author and
Book were destroyed at the end of the
main function. We eliminated the memory leak by using the
weak_ptr data members rather than the shared_ptr data members.
The weak_ptrs don't affect the reference
count but still allow us to access the resource when we need it by creating a
temporary shared_ptr to the resource. When the shared_ptrs we
created in main are destroyed, the
reference counts become zero and the instances of classes Author and
Book are deleted properly.











Fig. 21.14. weak_ptrs
used to prevent a memory leak in circularly referential data.

Reference count for Book C++ How to Program is 1
Reference count for Author Deitel & Deitel is 1

Access the Author's name and the Book's title through weak_ptrs.
Reference count for Author Deitel & Deitel is 2.
The book C++ How to Program was written by Deitel & Deitel

Reference count for Book C++ How to Program is 2.
Author Deitel & Deitel wrote the book C++ How to Program

Reference count for Book C++ How to Program is 1
Reference count for Author Deitel & Deitel is 1

The shared_ptrs are going out of scope.
Destroying Author: Deitel & Deitel
Destroying Book: C++ How to Program





 


No comments:

Post a Comment