Smart Pointers in Boost

Smart Pointers in Boost

 

 


 

Introducing Boost

 

According to the Boost website, Boost is “a repository of free, portable, peer-reviewed C++ libraries. Boost acts as a proving ground for new libraries, particularly those that work well with the ISO C++ Standard library.” But Boost is more than a collection of libraries. It is also the focus of a rapidly growing community of developers who create, use, and discuss the Boost libraries. Beyond making these excellent libraries available, Boost also offers a place to learn. The libraries are examples of solid library design in a world that seldom seems to look further than the next release. Joining the discussions on the Boost mailing list (either actively or just by listening) is a good way to improve your understanding of library design problems and solutions. Boost also provides a rapidly growing Boost-Users mailing list that focuses on questions related to using the libraries.

The quality and the technological standard of the Boost libraries are amazing. And the Boost portability standard ensures that when you move your code to another platform, you’ll know the libraries will still work. The current release, Boost 1.25.0, consists of libraries ranging from smart pointers to regular expressions to a portable threading library. Boost currently supports 35 libraries, all of which have been thoroughly reviewed by community members. These libraries can be freely used, and many of them are being used in commercial applications.

Boost is one of the strongest C++ communities. Among the 2,000 members are many of the world’s top C++ programmers. Members stay involved because they like working with some of the best minds in programming. They also know their efforts are bound to have an impact on the C++ community, since much of what you see in Boost is a strong candidate for inclusion in the next C++ Standard.

The best way to get acquainted with Boost is to tour the Boost libraries. In this article, I’ll introduce you to the Boost smart pointer library smart_ptr . smart_ptr is a good example of the innovation and sound design you’ll find in Boost. I encourage you to visit the Boost website (<www.boost.org>) for more on the other 34 libraries in the Boost collection.

 

Smart Pointers

 

One of the smaller (size-wise) Boost libraries is smart_ptr . smart_ptr is one of the libraries that I think is going to end up in the C++ Standard. This article discusses the Boost smart_ptr library. But first, I’ll begin with a brief introduction to smart pointers.

 

30-Second Introduction to Smart Pointers

 

Smart pointers are classes that store pointers to dynamically allocated (heap) objects. They behave much like built-in C++ pointers except that they automatically delete the object pointed to at the appropriate time. Smart pointers are particularly useful in the face of exceptions as they ensure proper destruction of dynamically allocated objects. They can also be used to keep track of dynamically allocated objects shared by multiple owners.

Actually, smart pointers can do more, such as handle thread safety, provide copy-on-write, ensure protocols, and provide remote communication services. There are ways to create generic smart pointers for these ESPs (Extremely Smart Pointers), but those won’t be covered here. (See [1] for in-depth coverage of this topic. By the way, Alexandrescu is currently considering submitting his C++ library Loki to Boost.)

Most uses of smart pointers are for lifetime control, period. They implement operator-> and operator* to yield the raw pointer, allowing the smart pointer to look like an ordinary pointer.

One such class comes with the standard library: std::auto_ptr . It is designed to handle ownership of a resource, but lacks support for reference counting and arrays. Also, std::auto_ptr transfers ownership when it is copied. In many cases, you need more and/or different functionality. Enter the smart_ptr classes.

 

smart_ptr Classes

 

The smart pointers in Boost are:

 

  • scoped_ptr , which handles sole ownership of single objects; unlike std::auto_ptr , scoped_ptr cannot be copied
  • scoped_array , which is similar to scoped_ptr , but handles arrays
  • shared_ptr , which allows object ownership to be shared
  • shared_array , which allows sharing of ownership for arrays

 

scoped_ptr

 

The scoped_ptr smart pointer differs from std::auto_ptr in that it doesn’t transfer ownership. In fact, it explicitly forbids any attempt to do so! This is important for any scenario where you need to be sure that there is only ever one owner to a pointer. Without scoped_ptr , you would probably be inclined to use std::auto_ptr , but take a look at the following code:

 

auto_ptr<string> MyOwnString
(new string("This is mine to keep!"));
auto_ptr<string> NoItsMine(MyOwnString);

cout << *MyOwnString << endl; // Boom

 

This code will obviously fail, since ownership of the string has been transferred to NoItsMine . This is not a design flaw in std::auto_ptr — it is a feature. However, when you need MyOwnString to be just that, you’ll want to use scoped_ptr :

 

scoped_ptr<string> MyOwnString
(new string("This is mine to keep for real!"));
// Compiler error - there is no copy constructor.
scoped_ptr<string> TryingToTakeItAnyway
(MyOwnString);

 

The scoped_ptr accomplishes this behavior by inheriting from boost::noncopyable (found in the library Boost.utility ). The non-copyable class declares the copy constructor and the assignment operator as private.

 

scoped_array

 

scoped_array is obviously the equivalent of scoped_ptr , but for arrays. You’ll get no help from the standard library here — unless of course you’re using std::vector , which is the right way to go in most cases.

The usage is similar to scoped_ptr :

 

typedef tuples::tuple<string, int> ArrayTuple;

scoped_array<ArrayTuple> MyArray(new ArrayTuple[10]);
tuples::get<0>(MyArray[5]) =
"The library Tuples is also part of Boost";

 

Tuples are collections of elements — for example pairs, triples, and quadruples. A typical use of tuples is returning multiple values from a function. The Boost Tuple Library can be thought of as an extended generalization of the standard library’s pair, and it currently works with up to 10 tuple elements. It supports tuple streaming, comparison, assignment, unpacking, and more. For more information about the Boost Tuple Library, see [2] and [3] .

As the scoped_array goes out of scope, delete[] will be properly called. This eliminates a common mistake, namely invoking the wrong operator delete .

 

shared_ptr

 

Here’s an offering that you won’t find in the standard library — a reference-counted smart pointer. Most of you have rolled your own smart pointers, and there is plenty of literature regarding the issues involved in reference counting. One of the most important details is how the reference count is implemented — intrusive, which means that you add support to the classes being reference counted, or non-intrusive, which means you don’t. The Boost shared_ptr is non-intrusive, and the implementation uses a reference counter allocated from the heap. There has been much discussion about providing parameterized strategies to best suit any given situation, but in the end it was decided against to focus on usability. Don’t expect the discussions to go away though.

The usage of shared_ptr does what you’d expect: it takes responsibility of deleting the pointee when there are no more users of the instance, and it shares its pointee freely.

 

void PrintIfString(const any& Any) {
if (const shared_ptr<string>* s =
any_cast<shared_ptr<string> >(&Any)) {
cout << **s << endl;
}
}

int main(int argc, char* argv[])
{
std::vector<boost::any> Stuff;

shared_ptr<string> SharedString1
(new string("Share me. By the way,
Boost.any is another useful Boost
library"));

shared_ptr<string> SharedString2
(SharedString1);
shared_ptr<int> SharedInt1
(new int(42));
shared_ptr<int> SharedInt2
(SharedInt1);

Stuff.push_back(SharedString1);
Stuff.push_back(SharedString2);
Stuff.push_back(SharedInt1);
Stuff.push_back(SharedInt2);

// Print the strings
for_each(Stuff.begin(), Stuff.end(),
PrintIfString);

Stuff.clear();

// The pointees of the shared_ptr’s
// will be released on leaving scope
return 0;
}

 

The any library offers a way to store just about anything [2] [4] . The requirements on contained types are that they are CopyConstructible , the destructor must not throw, and they should typically be Assignable . How do we store and pass “anything”? Indiscriminate types (read void* ) can refer to just about anything, but that means throwing type safety (and knowledge) out the door. Using any , type safety is preserved. All types satisfying the requirements for any can be assigned, but extraction demands knowing the exact type. any_cast is the key for unlocking the value held by any . any_cast works like dynamic_cast — casts to pointer types succeed or fail by returning a null pointer whereas casts to reference types throw an exception (bad_any_cast ) on failure.

 

shared_array

 

Again, shared_array is the equivalent of shared_ptr , but it is used with arrays.

 

shared_array<Base> MyStrings( new Base[20] );

 

Diving into the shared_ptr Implementation

 

Creating a simple smart pointer is fairly easy. Creating a smart pointer that will work with most compilers is more difficult. Creating a smart pointer with exception safety in mind is a lot more difficult. Boost::shared_ptr does it all, and here’s how. (Note: include s, broken compiler fixes, and parts of the implementation are omitted, but you’ll find them in Boost.smart_ptr ).

First, the class definition: obviously, smart pointers are (almost always) templates.

 

template<typename T> class shared_ptr {

 

The public interface:

 

explicit shared_ptr(T* p =0) : px(p) {
// fix: prevent leak if new throws
try { pn = new long(1); }
catch (...) { checked_delete(p); throw; }
}

 

Now, in the constructor there are two things that are easily forgotten. The constructor is explicit, just like most constructors that can take a single argument. The other important thing to note is that the heap allocation of the reference counter is protected by a try-catch block. Without it, you would have a flawed smart pointer that wouldn’t do its only job if the reference counter can’t be allocated.

 

  ~shared_ptr() { dispose(); }

 

The destructor has another important task: if the reference count goes down to zero, it should safely delete the pointee. The destructor delegates this task to another method: dispose .

 

void dispose() { if (—*pn == 0)
{ checked_delete(px); delete pn; } }

 

As you can see, the reference count (pn ) is decremented. If it comes down to zero, checked_delete is called on the pointee (px ), and then the reference count (pn ) is also deleted.

So, what does checked_delete do? This handy function (which you’ll find in Boost.utility ) makes sure that the pointer represents a complete type. Do you have that in your own smart pointer classes?

Now, here’s the first assignment operator:

 

template<typename Y> shared_ptr& operator=
(const shared_ptr<Y>& r) {
share(r.px,r.pn);
return *this;
}

 

This is a member template, and if it had not been, there are two scenarios:

 

  1. If there is no parameterized copy constructor, assignment of the type Base = Derived won’t work.
  2. If there is a parameterized copy constructor, assignments will work, but it involves creating an unnecessary temporary smart_ptr .

 

Again, this shows you a very good reason why you shouldn’t roll your own smart pointers — these are not obvious issues.

The actual work of this assignment operator is done by share :

 

void share(T* rpx, long* rpn) {
if (pn != rpn) { // Q: why not px != rpx?
// A: fails when both == 0
++*rpn; // done before dispose() in case
// rpn transitively dependent on
// *this (bug reported by Ken Johnson)
dispose();
px = rpx;
pn = rpn;
}
}

 

Note that the comparison for self assignment (or rather self sharing) is done by comparing the reference counters, not the pointers. Why? They both can be zero, but not necessarily the same.

 

template<typename Y> shared_ptr
(const shared_ptr<Y>& r) : px(r.px) { // never throws
++*(pn = r.pn);
}

 

This version is a templated copy constructor. See above for a good reason why.

The assignment operator and copy constructor also have non-templated versions:

 

shared_ptr(const shared_ptr& r) :
// never throws
px(r.px) { ++*(pn = r.pn); }
shared_ptr& operator=
(const shared_ptr& r) {
share(r.px,r.pn);
return *this;
}

 

The reset function is true to its name by resetting the pointee. Very handy if you need to destroy a pointee before leaving scope, or simply to invalidate some cached values.

 

  void reset(T* p=0) {
// fix: self-assignment safe
if ( px == p ) return;
if (—*pn == 0)
{ checked_delete(px); }
else { // allocate new reference
// counter
// fix: prevent leak if new throws
try { pn = new long; }
catch (...) {
// undo effect of —*pn above to
// meet effects guarantee
++*pn;
checked_delete(p);
throw;
} // catch
} // allocate new reference counter
*pn = 1;
px = p;
} // reset

 

Again, note the care that has been taken to avoid potential leaks and to maintain exception safety.

Then you have the operators that enable the “smart” of the smart pointer:

 

// never throws
T& operator*() const { return *px; }
// never throws
T* operator->() const { return px; }
// never throws
T* get() const { return px; }

 

Just a note: there are smart pointers that implement a cast operator to T* . This is not a good idea, and more often than not, you’ll burn your fingers. The inconvenience of having to write get disables the compiler from playing games with your sanity.

I think it was Andrei Alexandrescu who said “If your smart pointer behaves exactly like dumb pointers, they are dumb pointers.” True enough.

In closing, here are a couple of convenient functions.

 

long use_count() const  
{ return *pn; } // never throws
bool unique() const
{ return *pn == 1; } // never throws

 

The names are self-explanatory, right?

There is a lot more to mention about Boost.smart_ptr (such as specializations for std::swap and std::less , members for compatibility and convenience together with std::auto_ptr , etc.), but I’m running out of space. See smart_ptr.hpp in the Boost distribution (<www.boost.org>) for the gory details. Even without those extras, isn’t this indeed a very smart pointer?

 

Conclusion

 

This has been a short introduction to the world of Boost. Anyone is welcome to join, and frankly, I think that most C++ programmers have very good reasons to do so. I’d like to thank Beman Dawes, David Abrahams, and Jens Maurer for their help in answering questions and sharing their opinions (see sidebar, “Answers from the Original Boosters” ).

See you at Boost!

 

Notes and References

 

[1] Andrei Alexandrescu. Modern C++ Design (Addison-Wesley, 2001).

[2] Boost, <www.boost.org>.

[3] Jaako Jarvi. “Tuple Types and Multiple Return Values,” C/C++ Users Journal , August 2001.

[4] Jim Hyslop and Herb Sutter. “I’d Hold Anything for You,” C/C++ Users Journal C++ Experts Forum , December 2001, <www.cuj.com/experts/1912/hyslop.htm>.

[5] Boost mailing list, <http://groups.yahoo.com/group/Boost>.

[6] Boost-Users mailing list, <http://groups.yahoo.com/group/Boost-Users>.

[7] C++ Standard, International Standard ISO/IEC 14882.

 

Bjorn Karlsson is a professional software developer at ReadSoft. Bjorn can be reached at bjorn.karlsson@readsoft.com .

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值