Beyond the C++ Standard Library: An Introduction to Boost [Electronic resources] نسخه متنی

This is a Digital Library

With over 100,000 free electronic resource in Persian, Arabic and English

Beyond the C++ Standard Library: An Introduction to Boost [Electronic resources] - نسخه متنی

Bjorn Karlsson

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید

scoped_ptr


Header: "boost/scoped_ptr.hpp"


boost::scoped_ptr is used to ensure the proper deletion of a dynamically allocated object. scoped_ptr has similar characteristics to std::auto_ptr, with the important difference that it doesn't transfer ownership the way an auto_ptr does. In fact, a scoped_ptr cannot be copied or assigned at all! A scoped_ptr assumes ownership of the resource to which it points, and never accidentally surrenders that ownership. This property of scoped_ptr improves expressiveness in our code, as we can select the smart pointer (scoped_ptr or auto_ptr) that best fits our needs.

When deciding whether to use std::auto_ptr or boost::scoped_ptr, consider whether transfer of ownership is a desirable property of the smart pointer. If it isn't, use scoped_ptr. It is a lightweight smart pointer; using it doesn't make your program larger or run slower. It only makes your code safer and more maintainable.

Next is the synopsis for scoped_ptr, followed by a short description of the class members:


namespace boost {
template<typename T> class scoped_ptr : noncopyable {
public:
explicit scoped_ptr(T* p = 0);
~scoped_ptr();
void reset(T* p = 0);
T& operator*() const;
T* operator->() const;
T* get() const;
void swap(scoped_ptr& b);
};
template<typename T>
void swap(scoped_ptr<T> & a, scoped_ptr<T> & b);
}

Members



explicit scoped_ptr(T* p=0)

The constructor stores a copy of p. Note that p must be allocated using operator new, or be null. There is no requirement on T to be a complete type at the time of construction. This is useful when the pointer p is the result of calling some allocation function rather than calling new directly: Because the type needn't be complete, a forward declaration of the type T is enough. This constructor never throws.


~scoped_ptr()

Deletes the pointee. The type T must be a complete type when it is destroyed. If the scoped_ptr holds no resource at the time of its destruction, this does nothing. The destructor never throws.


void reset(T* p=0);

Resetting a scoped_ptr deletes the stored pointer it already owns, if any, and then saves p. Often, the lifetime management of a resource is completely left to be handled by the scoped_ptr, but on rare occasions the resource needs to be freed prior to the scoped_ptr's destruction, or another resource needs to be handled by the scoped_ptr instead of the original. In those cases, reset is useful, but use it sparingly. (Excessive use probably indicates a design problem.) This function never throws.


T& operator*() const;

Returns a reference to the object pointed to by the stored pointer. As there are no null references, dereferencing a scoped_ptr that holds a null pointer results in undefined behavior. If in doubt as to whether the contained pointer is valid, use the function get instead of dereferencing. This operator never throws.


T* operator->() const;

Returns the stored pointer. It is undefined behavior to invoke this operator if the stored pointer is null. Use the member function get if there's uncertainty as to whether the pointer is null. This operator never throws.


T* get() const;

Returns the stored pointer. get should be used with caution, because of the issues of dealing with raw pointers. However, get makes it possible to explicitly test whether the stored pointer is null. The function never throws. get is typically used when calling functions that require a raw pointer.


operator unspecified_bool_type() const

Returns whether the scoped_ptr is non-null. The type of the returned value is unspecified, but it can be used in Boolean contexts. Rather than using get to test the validity of the scoped_ptr, prefer using this conversion function to test it in an if-statement.


void swap(scoped_ptr& b)

Exchanges the contents of two scoped_ptrs. This function never throws.

Free Functions



template<typename T> void swap(scoped_ptr<T>& a,scoped_ptr<T>& b)

This function offers the preferred means by which to exchange the contents of two scoped pointers. It is preferable because swap(scoped1,scoped2) can be applied generically (in templated code) to many pointer types, including raw pointers and third-party smart pointers.[2] scoped1.swap(scoped2) only works on smart pointers, not on raw pointers, and only on those that define the operation.

[2] You can create your own free swap function for third-party smart pointers that weren't smart enough to provide their own.

Usage


A scoped_ptr is used like an ordinary pointer with a few important differences; the most important are that you don't have to remember to invoke delete on the pointer and that copying is disallowed. The typical operators for pointer operations (operator* and operator->) are overloaded to provide the same syntactic access as that for a raw pointer. Using scoped_ptrs are just as fast as using raw pointers, and there's no size overhead, so use them extensively. To use boost::scoped_ptr, include the header "boost/scoped_ptr.hpp". When declaring a scoped_ptr, the type of the pointee is the parameter to the class template. For example, here's a scoped_ptr that wraps a pointer to std::string:


boost::scoped_ptr<std::string> p(new std::string("Hello"));

When a scoped_ptr is destroyed, it calls delete on the pointer that it owns.

No Need to Manually Delete


Let's take a look at a program that uses a scoped_ptr to manage a pointer to std::string. Note how there's no call to delete, as the scoped_ptr is an automatic variable and is therefore destroyed as it goes out of scope.


#include "boost/scoped_ptr.hpp"
#include <string>
#include <iostream>
int main() {
{
boost::scoped_ptr<std::string>
p(new std::string("Use scoped_ptr often."));
// Print the value of the string
if (p)
std::cout << *p << '\n';
// Get the size of the string
size_t i=p->size();
// Assign a new value to the string
*p="Acts just like a pointer";
} // p is destroyed here, and deletes the std::string
}

A couple of things are worth noting in the preceding code. First, a scoped_ptr can be tested for validity, just like an ordinary pointer, because it provides an implicit conversion to a type that can be used in Boolean expressions. Second, calling member functions on the pointee works like for raw pointers, because of the overloaded operator->. Third, dereferencing scoped_ptr also works exactly like for raw pointers, thanks to the overloaded operator*. These properties are what makes usage of scoped_ptrand other smart pointersso intuitive, because the differences from raw pointers are mostly related to the lifetime management semantics, not syntax.

Almost Like auto_ptr


The major difference between scoped_ptr and auto_ptr is in the treatment of ownership. auto_ptr willingly transfers ownershipaway from the source auto_ptrwhen copied, whereas a scoped_ptr cannot be copied. Take a look at the following program, which shows scoped_ptr and auto_ptr side by side to clearly show how they differ.


void scoped_vs_auto() {
using boost::scoped_ptr;
using std::auto_ptr;
scoped_ptr<std::string> p_scoped(new std::string("Hello"));
auto_ptr<std::string> p_auto(new std::string("Hello"));
p_scoped->size();
p_auto->size();
scoped_ptr<std::string> p_another_scoped=p_scoped;
auto_ptr<std::string> p_another_auto=p_auto;
p_another_auto->size();
(*p_auto).size();
}

This example doesn't compile because a scoped_ptr cannot be copy constructed or assigned to. The auto_ptr can be both copy constructed and copy assigned, but that also means that it transfers ownership from p_auto to p_another_auto, leaving p_auto with a null pointer after the assignment. This can lead to unpleasant surprises, such as when trying to store auto_ptrs in a container.[3] If we remove the assignment to p_another_scoped, the program compiles cleanly, but it results in undefined behavior at runtime, because of dereferencing the null pointer in p_auto (*p_auto).

[3] Never, ever, store auto_ptrs in Standard Library containers. Typically, you'll get a compiler error if you try; if you don't, you're in trouble.

Because scoped_ptr::get returns a raw pointer, it is possible to do evil things to a scoped_ptr, and there are two things that you'll especially want to avoid. First, do not delete the stored pointer. It is deleted once again when the scoped_ptr is destroyed. Second, do not store the raw pointer in another scoped_ptr (or any smart pointer for that matter). Bad things happen when the pointer is deleted twice, once by each scoped_ptr. Simply put, minimize the use of get, unless you are dealing with legacy code that requires you to pass the raw pointer!

scoped_ptr and the Pimpl Idiom


scoped_ptr is ideal to use in many situations where one has previously used raw pointers or auto_ptrs, such as when implementing the pimpl idiom. and Exceptional C++ for more on the pimpl idiom.


// pimpl_sample.hpp
#if !defined (PIMPL_SAMPLE)
#define PIMPL_SAMPLE
struct impl;
class pimpl_sample {
impl* pimpl_;
public:
pimpl_sample();
~pimpl_sample();
void do_something();
};
#endif

That's the interface for the class pimpl_sample. The struct impl is forward declared, and it holds all private members and functions in the implementation file. The effect is that clients are fully insulated from the internal details of the pimpl_sample class.


// pimpl_sample.cpp
#include "pimpl_sample.hpp"
#include <string>
#include <iostream>
struct pimpl_sample::impl {
void do_something_() {
std::cout << s_ << "\n";
}
std::string s_;
};
pimpl_sample::pimpl_sample()
: pimpl_(new impl) {
pimpl_->s_ = "This is the pimpl idiom";
}
pimpl_sample::~pimpl_sample() {
delete pimpl_;
}
void pimpl_sample::do_something() {
pimpl_->do_something_();
}

At first glance, this may look perfectly fine, but it's not. The implementation is not exception safe! The reason is that the pimpl_sample constructor may throw an exception after the pimpl has been constructed. Throwing an exception in the constructor implies that the object being constructed never fully existed, so its destructor isn't invoked when the stack is unwound. This state of affairs means that the memory allocated and referenced by the impl_ pointer will leak. However, there's an easy cure for this; scoped_ptr to the rescue!


class pimpl_sample {
struct impl;
boost::scoped_ptr<impl> pimpl_;
...
};

By letting a scoped_ptr handle the lifetime management of the hidden impl class, and after removing the deletion of the impl from the destructor (it's no longer needed, thanks to scoped_ptr), we're done. However, you must still remember to define the destructor manually; the reason is that at the time the compiler generates an implicit destructor, the type impl is incomplete, so its destructor isn't called. If you were to use auto_ptr to store the impl, you could still compile code containing such errors, but using scoped_ptr, you'll receive an error.

Note that when using scoped_ptr as a class member, you need to manually define the copy constructor and copy assignment operator. The reason for this is that a scoped_ptr cannot be copied, and therefore the class that aggregates it also becomes noncopyable.

Finally, it's worth noting that if the pimpl instance can be safely shared between instances of the enclosing class (here, pimpl_sample), then boost::shared_ptr is the right choice for handling the pimpl's lifetime. The advantages of using shared_ptr rather than scoped_ptr includes being relieved from manually defining the copy constructor and copy assignment operator, and to define an empty destructorshared_ptr is designed to work correctly even with incomplete types.

scoped_ptr Is Not the Same As const auto_ptr


The observant reader has probably already noted that an auto_ptr can indeed be made to work almost like a scoped_ptr, by declaring the auto_ptr const:


const auto_ptr<A> no_transfer_of_ownership(new A);

It's close, but not quite the same. The big difference is that a scoped_ptr can be reset, effectively deleting and replacing the pointee when needed. That's not possible with a const auto_ptr. Another difference, albeit smaller, is the difference in names: Although const auto_ptr essentially makes the same statement as scoped_ptr, it does so more verbosely and less obviously. After you have scoped_ptr in your vocabulary, you should use it because it clearly declares your intentions. If you want to say that a resource is scoped, and that there's no way you'll relinquish ownership of it, spell it boost::scoped_ptr.

Summary


Raw pointers complicate writing exception-safe and error-free code. Automatically limiting the lifetime of dynamically allocated objects to a certain scope via smart pointers is a powerful way to address those issues and also increase the readability, maintainability, and quality of your code. scoped_ptr unambiguously states that its pointee cannot be shared or transferred. As you've seen, std::auto_ptr can "steal" the pointee from another auto_ptr, even inadvertently, which is considered to be auto_ptr's biggest liability. That liability is what makes scoped_ptr such an excellent complement to auto_ptr. When a dynamically allocated object is passed to a scoped_ptr, it assumes sole ownership of that object. Because a scoped_ptr is almost always allocated as an automatic variable or data member, it is properly destroyed when it leaves scope, and thus frees its managed memory, when execution flow leaves a scope due to a return statement or a thrown exception.

Use scoped_ptr when

A pointer is used in a scope where an exception may be thrown

There are several control paths in a function

The lifetime of a dynamically allocated object can be limited to a specific scope

Exception safety is important (always!)

/ 124