|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
ContentsSmart Pointers can greatly simplify C++ development. Chiefly, they provide automatic memory management close to more restrictive languages (like C# or VB), but there is much more they can do. I already know smart pointers, but why should I use boost?
What are Smart Pointers?The name should already give it away:
"No longer needed" is hard to define, since resource management in C++ is very complex. Different smart pointer implementations cover the most common scenarios. Of course, different tasks than just deleting the object can be implemented too, but these applications are beyond the scope of this tutorial. Many libraries provide smart pointer implementations with different advantages and drawbacks. The samples here use the BOOST library, a high quality open source template library, with many submissions considered for inclusion in the next C++ standard. Boost provides the following smart pointer implementations:
Let's start with the simplest one: The first: boost::scoped_ptr<T>
The following sample uses a
Using "normal" pointers, we must remember to delete it at every place we exit the function. This is especially tiresome (and easily forgotten) when using exceptions. The second example uses a The advantage is obvious: in a more complex function, it's easy to forget to delete an object.
Reference counting pointersReference counting pointers track how many pointers are referring to an object, and when the last pointer to an object is destroyed, it deletes the object itself, too. The "normal" reference counted pointer provided by boost is void Sample2_Shared() { // (A) create a new CSample instance with one reference boost::shared_ptr<CSample> mySample(new CSample); printf("The Sample now has %i references\n", mySample.use_count()); // should be 1 // (B) assign a second pointer to it: boost::shared_ptr<CSample> mySample2 = mySample; // should be 2 refs by now printf("The Sample now has %i references\n", mySample.use_count()); // (C) set the first pointer to NULL mySample.reset(); printf("The Sample now has %i references\n", mySample2.use_count()); // 1 // the object allocated in (1) is deleted automatically // when mySample2 goes out of scope } Line (A) creates a new
Then, we assign it to a second pointer
We reset the first pointer (equivalent to
Only when the last reference,
Of course, this is not limited to a single
Note: If you never heard of PIMPL (a.k.a. handle/body) or RAII, grab a good C++ book - they are important concepts every C++ programmer should know. Smart pointers are just one way to implement them conveniently in certain cases - discussing them here would break the limits of this article. Important FeaturesThe
Example: Using shared_ptr in containersMany container classes, including the STL containers, require copy operations (e.g., when inserting an existing element into a list, vector, or container). However, when this copy operations are expensive (or are even unavailable), the typical solution is to use a container of pointers: std::vector<CMyLargeClass *> vec; vec.push_back( new CMyLargeClass("bigString") ); However, this throws the task of memory management back to the caller. We can, however, use a typedef boost::shared_ptr<CMyLargeClass> CMyLargeClassPtr; std::vector<CMyLargeClassPtr> vec; vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) ); Very similar, but now, the elements get destroyed automatically when the vector is destroyed - unless, of course, there's another smart pointer still holding a reference. Let's have a look at sample 3: void Sample3_Container() { typedef boost::shared_ptr<CSample> CSamplePtr; // (A) create a container of CSample pointers: std::vector<CSamplePtr> vec; // (B) add three elements vec.push_back(CSamplePtr(new CSample)); vec.push_back(CSamplePtr(new CSample)); vec.push_back(CSamplePtr(new CSample)); // (C) "keep" a pointer to the second: CSamplePtr anElement = vec[1]; // (D) destroy the vector: vec.clear(); // (E) the second element still exists anElement->Use(); printf("done. cleanup is automatic\n"); // (F) anElement goes out of scope, deleting the last CSample instance } What you absolutely must know to use boost smart pointers correctlyA few things can go wrong with smart pointers (most prominent is an invalid reference count, which deletes the object too early, or not at all). The boost implementation promotes safety, making all "potentially dangerous" operations explicit. So, with a few rules to remember, you are safe. There are a few rules you should (or must) follow, though: Rule 1: Assign and keep - Assign a newly constructed instance to a smart pointer immediately, and then keep it there. The smart pointer(s) now own the object, you must not delete it manually, nor can you take it away again. This helps to not accidentally delete an object that is still referenced by a smart pointer, or end up with an invalid reference count. Rule 2: a This means:
Rule 2: No circular references - If you have two objects referencing each other through a reference counting pointer, they are never deleted. boost provides Rule 3: no temporary shared_ptr - Do not construct temporary Cyclic ReferencesReference counting is a convenient resource management mechanism, it has one fundamental drawback though: cyclic references are not freed automatically, and are hard to detect by the computer. The simplest example is this: struct CDad; struct CChild; typedef boost::shared_ptr<CDad> CDadPtr; typedef boost::shared_ptr<CChild> CChildPtr; struct CDad : public CSample { CChildPtr myBoy; }; struct CChild : public CSample { CDadPtr myDad; }; // a "thing" that holds a smart pointer to another "thing": CDadPtr parent(new CDadPtr); CChildPtr child(new CChildPtr); // deliberately create a circular reference: parent->myBoy = child; child->myDad = dad; // resetting one ptr... child.reset();
If we now call The problem is not solvable with a "better" shared pointer implementation (or at least, only with unacceptable overhead and restrictions). So you have to break that cycle. There are two ways:
Solutions (1) and (2) are no perfect solutions, but they work with smart pointer libraries that do not offer a Using weak_ptr to break cyclesStrong vs. Weak References: A strong reference keeps the referenced object alive (i.e., as long as there is at least one strong reference to the object, it is not deleted). Note that a raw C++ pointer in this sense is a weak reference. However, if you have just the pointer, you have no ability to detect whether the object still lives.
struct CBetterChild : public CSample { weak_ptr<CDad> myDad; void BringBeer() { shared_ptr<CDad> strongDad = myDad.lock(); // request a strong pointer if (strongDad) // is the object still alive? strongDad->SetBeer(); // strongDad is released when it goes out of scope. // the object retains the weak pointer } }; See the Sample 5 for more. intrusive_ptr - lightweight shared pointer
To use a type #include "boost/intrusive_ptr.hpp" // forward declarations class CRefCounted; namespace boost { void intrusive_ptr_add_ref(CRefCounted * p); void intrusive_ptr_release(CRefCounted * p); }; // My Class class CRefCounted { private: long references; friend void ::boost::intrusive_ptr_add_ref(CRefCounted * p); friend void ::boost::intrusive_ptr_release(CRefCounted * p); public: CRefCounted() : references(0) {} // initialize references to 0 }; // class specific addref/release implementation // the two function overloads must be in the boost namespace on most compilers: namespace boost { inline void intrusive_ptr_add_ref(CRefCounted * p) { // increment reference count of object *p ++(p->references); } inline void intrusive_ptr_release(CRefCounted * p) { // decrement reference count, and delete object when reference count reaches 0 if (--(p->references) == 0) delete p; } } // namespace boost This is the most simplistic (and not thread safe) implementation. However, this is such a common pattern, that it makes sense to provide a common base class for this task. Maybe another article ;) scoped_array and shared_arrayThey are almost identical to Installing BoostDownload the current boost version from boost.org, and unzip it to a folder of your choice. The unzipped sources use the following structure (using my folders):
I add this folder to the common includes of my IDE:
Since the actual headers are in the boost\ subfolder, my sources has Note about the sample projectThe sample project contains a sub folder boost\ with a selection of boost headers required. This is merely so you can download and compile the sample. You should really download the complete and most current sources (now!). VC6: the min/max tragedyThere is a "little" problem with VC6 that makes using boost (and other libraries) a bit problematic out of the box. The Windows header files define macros for boost tries to fix that as good as they can, but sometimes you will run into problems. If this happens, here's what I do: put the following code before the first #define _NOMINMAX // disable windows.h defining min and max as macros #include "boost/config.hpp" // include boosts compiler-specific "fixes" using std::min; // makle them globally available using std::max; This solution (as any other) isn't without problems either, but it worked in all cases I needed it, and it's just one place to put it. ResourcesNot enough information? More Questions?
Articles on Code Project:
Please note: While I am happy about (almost) any feedback, please do not ask boost-specific questions here. Simply put, boost experts are unlikely to find your question here (and I'm just a boost noob). Of course, if you have questions, complaints, or recommendations regarding the article or the sample project, you are welcome. History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||