Using C++ 11's Smart Pointers

来源:互联网 发布:内存条涨价 知乎 编辑:程序博客网 时间:2024/06/06 01:09

Using C++ 11’s Smart Pointers

This tutorial deals with C++ 11’s smart pointer, which consists unique_ptr, shared_ptr, and its partner, weak_ptr.
本文摘自David Kieras, EECS Department, University of Michigan,Using C++ 11’s Smart Pointers. 建议阅读原文

Concept of the C++ 11 Smart Pointers

Smart pointers are class objects that behave like bulit-in pointers but also manage objects that you create with new.

How to Access the C++ 11 Smart Pointers

In a C++ 11 implementation, the following #include is all that is needed

#include <memory>

Shared Ownership with shared_ptr


The shared_ptr class template is a refereced-counted smart pointer.
* managed object
* manager object

managed object & manager object

注释:在用new operator初始化一个shared_ptr时,会产生一个manager object来管理生成的managed object。


If the weak count is greater than zero, the manager object is kept. If the weak count is decremented to zero, and the shared count is also zero, the weak_ptr destructor deletes the manager object. Thus the managed object stays around as long as there are shared_ptrs pointing to it, and the manager object stays around as long as there are either shared_ptrs or weak_ptrs referring to it.


If the pointer and shared count are non-zero, then the managed object is still present, and weak_ptr can make the pointer to it available. This is done by a weak_ptr member function that creates and returns a new shared_ptr to the object; the new shared_ptr increments the shared count, which ensures that the managed object will stay in existence as long as necessary

Important restrictions in using shared_ptr and weak_ptr

  • You can only use these smart pointers to refer to objects allocated with new and that can be deleted with delete. No pointing to objects on the function call stack! Trying to delete them will cause a runtime error!. (basic rules, always create object with new operator when using smart pointers)
    注释:红色为批注

  • You must ensure that there is only one manager object for each managed object. You do this by writing your code so that when an object is first created, it is immediately given to a shared_ptr to manage , and any other shared_ptrs or weak_ptrs that are needed to point to that object are all directly or indirectly copied or assigned from that first shared_ptr. The customary way to ensure this is to write the new object expression as the argument for a shared_ptr constructor, or use the make_shared function template described below. (RAII, use a foolproof way to ensure you create smart pointers in right way)

  • If you want to get the full benefit of smart pointers, your code should avoid using raw pointers to refer to the same objects; otherwise it is too easy to have problems with dangling pointers or double deletions. In particular, smart pointers have a get() function that returns the pointer member variable as a built-in pointer value. This function is rarely needed. As much as possible, leave the built-in pointers inside the smart pointers and use only the smart pointers. (never use raw pointers when you wrap it with a smarter pointer, unless you have a good reason)
    注释:scott meyer的effective c++系列有讲到什么时候会用raw pointer.

Using shared_ptr

Basic use of shared_ptr

can be used as a call-by-value function argument or return value, or stored in containers. You can also reset a shared_ptr by assigning it the value nullptr, which is converted to an empty shared_ptr before the assignment (decrease count in advance, so may release memory in advance).
注释:下面是shared_ptr的基本用法。

#include <iostream>#include <memory>using namespace std;class Widget {public:  void foo();};ostream& operator<< (ostream&, const Widget&);// a function can return a shared_ptrshared_ptr<Widget> bar();// a function can take a shared_ptr by valueshared_ptr<Widget> do_something_with(shared_ptr<Widget> p);void foo(){  shared_ptr<Widget> p1(new Widget());  // the new is in the shared_ptr constructor expression  shared_ptr<Widget> p2 = p1;           // p1 and p2 now share ownership of the Widget  shared_ptr<Widget> p3(new Widget());  // another Widget  p1 = bar();                           // p1 may no longer pointer to the first thing  do_something_with(p2);  p3->foo();                            // call a member funtion like built-in pointer  cout << *p2 << endl;                  // deference like built-in pointer  p1.reset();                           // reset with a member funtion or assignment to nullptr  p2 = nullptr;                         // convert nullptr to an empty shared_ptr, and decrement count}
  • the only way to get a shared_ptr to take an address from a raw pointer is with the constructor
  • The only way you can get the raw pointer inside the manager object is with a member function, get() - there is no implicit conversion to the raw pointer type.

Inheritance and shared_ptr

The constructors and assignment operators in shared_ptr (and weak_ptr) are defined with templates so that if the built-in pointers could be validly copied or assigned, then the corresponding shared_ptrs can be also:

class Base {}; class Derived : public Base {}; shared_ptr<Derived> dp1(new Derived());shared_ptr<Base> bp1 = dp1;shared_ptr<Base> bp2(dp1);shared_ptr<Base> bp3(new Derived());

Should define virtual base destructor
注释:shared_ptr与raw pointer基本有着相同的行为,这里跑题一下,绝对不要在数组中使用多态。

Casting shared_ptrs


C++11 supplies some function templates that provide a casting service corresponding to the built-in pointer casts. These functions internally call the get() function from the supplied pointer, perform the cast, and return a shared_ptr of the specified type.

shared_ptr<Base> base_ptr (new Base);shared_ptr<Derived> derived_ptr; // if static_cast<Derived *>(base_ptr.get()) is valid, then the following is vaild:derived_ptr = static_pointer_cast<Derived>(base_ptr);

Testing and comparing shared_ptrs

You can compare two shared_ptrs using the ==, !=, and < operators; they compare the internal raw pointers and so behave just like these operators between built-in pointers. In addition, a** shared_ptr provides a conversion to a bool**. (operator int() or may other…)

Getting better memory allocation performance

shared_ptr<Widget> p(new Widget());

There are actually ***two dynamic memory allocations*** that happen: one for the object itself from the new, and then a second for the manager object created by the shared_ptr constructor.

To address this problem, C++11 includes a function template make_shared that does a single memory allocation big enough to hold both the manager object and the new object, passing along any constructor parameters that you specify, and returns a shared_ptr of the specified type, which can then be used to initialize the shared_ptr that you are creating (withefficient move semantics).

So instead of:

shared_ptr<Thing> p(new Thing); // ouch - two allocations
you would write:
shared_ptr<Thing> p(make_shared<Thing>()); // only one allocation!

Using make_shared also avoids explicit use of new, promoted in the slogan “no naked new!” That is, if you are using shared_ptrs throughout a project, never writing new, and always using make_shared, is an easy way to make sure that all allocated objects are managed by shared_ptrs.

Using weak_ptr

You can’t dereference it; neither operator* nor operator-> is defined for a weak_ptr. You can’t access the pointer to the object with it - there is no get() function. There is a comparison function defined so that you can store weak_ptrs in an ordered container; but that’s all.

Initializing a weak_ptr

You can point a weak_ptr to an object only by copy or assignment from a shared_ptr or an existing weak_ptr to the object.
注释:以下是weak_ptr的用法,用weak_ptr可以解决shared_ptr环的问题,这个代码我会补上。

shared_ptr<Widegt> wp1(sp);         // construct wp1 from a shared_ptrweak_ptr<Widegt> wp2;               // an empty weak_ptr - points to nothingwp2 = sp;                           // wp2 now points to the new Widegtweak_ptr<Widegt> wp3(wp2);          // construct wp3 from a weak_ptrweak_ptr<Widegt> wp4;wp4 = wp2;                          // wp4 now points to the new Widegt

You can use the reset() member function to set a weak_ptr back to the empty state in which it is pointing to nothing. (Unlike shared_ptr, you can’t reset a weak_ptr by assignment to nullptr)

Three ways to check a non-existent object

void do_it(weak_ptr<Widget> wp) {    shared_ptr<Widget> sp = wp.lock();      // get shared_ptr from weak_ptr    if(sp)      sp->foo();    else      cout << "The Widget is gone!" << endl;  }   

This is the most useful and common way to use a weak_ptr to access the object.注释:这个安全。
2.

bool is_it_there(weak_ptr<Widget> wp) {  if(wp.expired()) {    cout << "The thing is gone!" << endl;    return false;  }   return true}

This approach is useful as a way to simply ask whether the pointed-to object still exists. Notice that if after calling expired(), the code goes on to use lock() to get a shared_ptr to the object, testing first for expired() is redundant and may actually be problematic.

3.
We can construct a shared_ptr from a weak_ptr; if the weak_ptr is expired, an exception is thrown, of type
std::bad_weak_ptr. This has its uses, but the first method is generally handier and more direct.

Unique Ownership with unique_ptr

What makes the ownership unique?

The unique ownership is enforced by disallowing (with =delete) copy construction and copy assignment.

Transferring ownership

**the move constructor and move assignment operator are defined for unique_ptr **so that they transfer ownership from the original owner to the new owner. After move construction, the newly created unique_ptr owns the object and the original unique_ptr owns nothing.

Explicit transfer of ownership between unique_ptrs

unique_ptr<Widget> p1(new Widget());          // p1 owns the Widgetunique_ptr<Widget> p2;                        // p2 owns nothing// invoke move assignment explicitlyp2 = std::move(p1);                           // now p2 owns it, p1 onws nothing// invoke move construction explicitlyunique_ptr<Widget> p3(std::move(p2));         // now p3 owns it, p2 and p1 own nothing

注释:使用uniqe_ptr是IMPL手法的一个良好实现,在move constructor中直接move所指向的资源,另外uniqe_ptr也可以替换copy-swap idiom,真是太棒了!

Using unique_ptr with Standard Containers

  • You must fill the container by supplying rvalue unique_ptrs, so the ownership transfers into the unique_ptr in the container. Either use an unnamed rvalue unique_ptr as the argument for the container inserting/filling function, or use std::move with a named unique_ptr.
  • If you erase an item in the container, you are destroying the unique_ptr, which will then delete the object it is pointing to.
  • If you empty, clear, or destroy the container, all of the pointed-to objects will be deleted because all the unique_ptrs will be destroyed.
  • If you transfer ownership out of container items, the empty unique_ptrs stay in the container. If you leave empty unique_ptrs in the container, your code will need to check for empty unique_ptrs before dereferencing them.
  • You can refer to an object by referring to the unique_ptr in the container without trying to copy or take it out of the container.

Conclusion

The most common use of unique_ptr is as a pretty fool-proof way of making sure an object allocated in a function (or class constructor) gets deleted. However, there are situations in which ownership of objects needs to be transferred around but always remains in one place at a time; unique_ptr gives you way to represent this concept directly.

0 0