智能指针

来源:互联网 发布:淘宝直通车如何操作 编辑:程序博客网 时间:2024/05/19 00:16

how to Access the C++11 smart pointers

#include<memory>

shared ownership with shared_ptr

shared_ptr 类模板是一个 referenced-counted 智能指针;一个计数器在计数有多少个指针指向 managed objects;

当最后一个智能指针释放,计数为0,此时对象就自动释放。

                                             

然而有个问题是,当智能指针成环形或者循环,它们互相让对方"活着".

                                   

 C++ 11 采用 “弱化”的智能指针:这个指针仅仅观察对象,而不影响对象的生命周期。如果实际的智能指针为空,

对象就会被释放。

                                            

它们是怎样做到的呢?

                                                  

当第一个 shared_ptr 创建来指向对象的时候, shared_ptr 同时创建了一个管理对象。Manager object 包含一个指针指向对象;shared_ptr 就是通过这个指针访问对象的。Manager object 同时包含两个引用计数器。即 shared count, weak count; 当只有一个智能指针,sp1 的时候,shared count 为1, weak count 为0.如果有另外的一个 shared_ptr(sp2) 从sp1 复制过来时,sp2 也指向了 对象, 复制构造函数和赋值构造函数把 shared count 计数加一 即为2. 同样的道理,当weak pointer也被复制或赋值的时候, weak count 计数也加1.

类似的,当析构智能指针的时候,计数器就减少1.当 shared_ptr  为0 ,则managed  object 删除对象。当 shared count 为0 ,并且 weak count 为0,此时 manager object 才会被释放,什么也没有留下。如果shared count为0, 而 weak count 不为0, manager object 是会被保留的。

原文如下:

 the managed object stays around as long as there shared _ptrs pointing to it, and the manager object stays around as long as there are either shared_ptrs or weak_ptrs refering to is.

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 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. in this way ,the weak_ptr can point to an object without affecting its lifetime, but still make it easy to refer to the object, and at the same time, ensure that is stays around if someone is interested in it.

using shared_ptr

就想普通的指针那样,可以复制,赋值给另外的 shared_ptr, 即可以用作函数的参数值传递,返回值,或者存储在容器中。

reset()  引用指数减少,如果减少到0, 则释放对象。 另外,通过赋值 nullptr, 也可以构建一个空的 shared_ptr.

<span style="font-family:Microsoft YaHei;"><pre name="code" class="cpp">class Thing{  public:       void defrangulate();}:ostream& operator<< (ostream&, const Thing&);// a function can return a shared_ptrshared_ptr<Thing> find_some_thing();// a function can take a shared_ptr parameter by value;shared_ptr<Thing> do_something_with(shared_ptr<Thing>p);...void foo(){  //the new is in the shared_ptr constructor expression:  shared_ptr<Thing> pl(new Thing);...  shared_ptr<Thing> p2 = p1;  // p1 and p2 are share owership of the Thine  shared_ptr<Thing> p3(new Thing); //another Thing   p1 = find_some_thing();     //p1 may no longer point to first Thing   do_something_with(p2);   p3->defrangulate();     // call a member function  cout<< *p2 << endl;      // dereference like built-in pointer  // reset with a member function or assignment to nullptr  p1.reset();   // decrement cout, delete if last   p2 =  nullptr; //convert nullptr to an empty shared_ptr, and decrement count;}</span>


注意,不能再 shared_ptr 和 普通指针之间相互赋值,可以这样  Thing* raw_ptr = sp.get();

另外,两个 shared_ptr 之间可以用 ==, !=, 以及 <。 作为 if(shared_ptr) 为真仅当shared_ptr 有指向对象。

shared_ptr<Thing> p(new Thing)  该语句中,其实是有两个动态的内存分配,一个是new 出来的对象,一个是 shared_ptr 构造函数建造的指针管理对象。 这意味着,创建该指针比创建普通的指针费时间。鉴于此,C++11 使用 make_shared 来创建一个足够大的内存可以放上述的两个对象。

shared_ptr<Thing>p(make_shared<Thing>()); // only one allocation! 

using weak_ptr

weak_ptr 仅仅是用来观察 管理对象,不影响其生命周期。weak_ptr 的设计初衷所致,我们控制它还是很有限的。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.

下面的例子中,我们创建了一个shared_ptr, 然后展示不同的方法让 weak_ptr 也指向 Thing 对象。 确保weak_ptr 总是指向shared_ptr 创建的管理对象。

<span style="font-family:Microsoft YaHei;">shared_ptr<Thing> sp(new Thing);weak_ptr<Thing> wp1(sp);        //construct wp1 from a shared_ptrweak_ptr<Thing> wp2;       // an empty weak_ptr points to nothingwp2 = sp;             //wp2 now points to the new Thingweak_ptr<Thing>wp3 (wp2); // construct wp3 from a weak_ptrweak_ptr<Thing>wp4wp4 = wp2;                // wp4 now points to the new Thing</span>


Special Case: Getting a shared_ptr for "this" Object

假设有一种情况,Thing 的一个成员函数需要传递一个 this 指针到另外的一个函数, 如果不用智能指针,没有问题,如下:

<span style="font-family:Microsoft YaHei;">Class Thing{public:       void foo();       void defrangulate();};void transmogrify(Thing *);int main(){   Thing* t1 = new  Thing;   t1->foo();   ...   delete t1;   // done with the object} ...void Thing::foo(){   // we need to transmogrify this object   tranfmogrify(this);}...void transmogrify(Thing* ptr){   ptr->defrangulate();   /* etc. */}</span>
此时,我们需要使用智能指针,这样可以自动的管理Thing内存。 如果只是简单的把 raw 指针 换成 稚嫩指针 Thing* to shared_ptr<Thing>, 那么下面的代码会编译通过,可是有大问题:

<span style="font-family:Microsoft YaHei;">Class Thing{public:      void foo();      void defrangulate();};void transmogrify(shared_ptr<Thing>);int main(){  shared_ptr<Thing> t1(new Thing); // start a manager object for the Thing  t1 -> foo();  ....  // Thing is supposed to get deleted when t1 goes out of scope}...void Thing::foo(){   // we need to transmogrify this object   shared_ptr<Thing> sp_for_this(this); // danger! a second manager object!   transmogrify(sp_for_this);}....void transmogrify(shared_ptr<Thing> ptr){  ptr-> defrangulate();  /* etc. */}</span>
当主函数mian 中创建 shared_ptr  t1 的时候,一个Thing 的管理对象 manager object 也同时创建了。但是在函数 Thing::foo() 我们也为this 对象创建了一个shared->ptr <Thing> sp_for_this 指针,同时也有一个管理对象 manager object。这有什么问题呢? 当sp_for_this 指针离开{}, Thing 对象就会被释放。留下 t1 指向一个不存在的Thing。解决的办法就是,使用 weak_ptr. Thing 对象有个weak_ptr 指向创建的manager objcet . 这样在创建 sp_for_this 

Thing 类必须 继承 enabled_shared_from_this<Thing>,这样它就有weak_ptr<Thing>作为成员变量。当Thing 对象的首个 智能指针 shared_ptr 被创建的时候shared_ptr 构造函数发现enable_shared_from_this 基类,就会把weak_ptr作为成员变量, 并和shared_ptr 同时指向 manager object. 这样在创建一个 sp_for_this 的时候,构造函数就会通过weak_ptr 来实现, 这样,就只有一个manager object。

                                            

其他不变,代码第一行变下:

<span style="font-family:Microsoft YaHei;">class Thing: public enable_shared_from_this<Thing>{...}</span>

Unique Ownership with unique_ptr

使用 unique_ptr, 我们可以指向一个对象,当unique_ptr 离开{},那么它指向的对象也会被释放了。不管是正常退出还是异常退出,都会释放对象。

<span style="font-family:Microsoft YaHei;">void foo(){  unique_ptr<Thing> p(new Thing); //p owns the Thing  p->do_something();  //tell the thing to do something  defrangulate();  //might throw an excepting} //p gets destroyed; destructor deltes the Thing</span>

使用 unique_ptr 有什么好处呢?

1. unique_ptr 的实现机制非常的简单,不需要像 shared_ptr 那样有manager object.

2. unique_ptr 实现了所有权的独享概念,某一时刻,只能是一个unique_ptr 占有对象。

what makes the ownership unique?

对兑现的独享机制是通过禁止 赋值构造 和拷贝构造函数来实现的。 所以 unique_ptr 指针之间是不能像 shared_ptr  那样相互之间赋值的。

<span style="font-family:Microsoft YaHei;">unique_ptr<Thing> p1(new Thing); // p1 owns the Thingunique_ptr<Thing>p2(p1);   //error - copy construction is not allowedunique_ptr<Thing>p3;   // an empty unique_ptr;p3 = p1;   //error, copy assignment is not allowed;</span>


Transferring ownership

在unique_ptr 之间 ownership 是可以转移的。unique_ptr 中定义了 move constructor 和 move assignment operator , 从而可以实现转移。

<span style="font-family:Microsoft YaHei;">//create a Thing and return a unique_ptr to itunique_ptr<Thing> create_Thing(){   unqiue_ptr<Thing> local_ptr(new Thing);   return local_ptr; //local_ptr will surrender ownership}void foo(){  unique_ptr<Thing> p1(create_Thing()); // move ctor from returned rvalue  // p1 now owns the Thing  unique_ptr<Thing> p2;  //default ctor'd; owns nothing  p2 =  create_Thing();    // move assignment from returned rvalue  // p2 now owns the second Thing}</span>

还可以使用std::move() 

<span style="font-family:Microsoft YaHei;">unique_ptr<Thing>p1(new Thing); //p1 owns the Thingunique_ptr<Thing>p2 ; //p2 owns nothing//invoke move assignment explicityp2 = std::move(p1);  //now p2 owns it, p1 owns nothing//invoke move construction explicityunique_ptr<Thing> p3(std::move(p2));  // now p3 owns it, p2 and p1 own nothing</span>

总结下,终极测试版:

<span style="font-family:Microsoft YaHei;">class  Thing:public enable_shared_from_this<Thing>{public:   void defrangulate();   void  foo();};void transmogrify(shared_ptr<Thing> ptr);void Thing::defrangulate(){cout<<"just do it"<<endl;}void Thing::foo(){   //we need to transmogrify this object// get a shared_ptr from the weak_ptr in this objectshared_ptr<Thing> sp_this= shared_from_this();transmogrify(sp_this);}void transmogrify(shared_ptr<Thing> ptr){ptr->defrangulate();}void main(){//test for shared_ptrshared_ptr<Thing>p1(new Thing);shared_ptr<Thing> p2 = p1;if(p1 == p2)cout<<"two qual shared_ptrs"<<endl;if(p1)cout<<"none dangling pointer"<<endl;p2->defrangulate();p2->foo();p1.reset();p2 = nullptr;// test for weak_ptrshared_ptr<Thing>sp(new Thing);weak_ptr<Thing>wp1(sp);  // construct wp1 form a shared_ptrweak_ptr<Thing>wp2;wp2 = sp;       //wp2 now point to Thingweak_ptr<Thing>wp3(wp2);  //construct wp3 from a weak_ptrshared_ptr<Thing>sp2 =wp2.lock();if(sp2)   sp2->defrangulate();elsecout<<"constructor form weak_ptr error"<<endl;//test for unique_ptrunique_ptr<Thing>up1(new Thing); //p1 owns the Thing//unique_ptr<Thing>p2(p1);  //errot  copy construction is not allowedunique_ptr<Thing>up3;   // an empty unique_ptr;// p3 =p1;    //error, cppy assignment is not allowedup3 = std::move(up1);if(up3)up3->defrangulate();    }</span>


执行结果如下:
                              


using auto_ptr 

<span style="font-family:Microsoft YaHei;">template<class T> class auto_ptr{  T* ptr; public:    explicit auto_ptr(T* p =0) : ptr(p) {}   ~ auto_ptr()                      {delete ptr;}   T& operator*()                  {return *ptr;}    T* operator ->()               {return ptr;}}</span>

可以看到,auto_ptr 仅仅是在常规指针外的包装纸。设计的巧妙之处在于 析构的时候不用操心 释放 指针。

<span style="font-family:Microsoft YaHei;">void foo(){  MyClass* p(new MyClass)  p->Dosomething();  delete p;}</span>

可以改成

<span style="font-family:Microsoft YaHei;">void foo(){  auto_ptr<MyClass>p(new MyClass);  p->DoSomething();}</span>


Why would I use them?

一. 更少的bug

自动清空: 因为自动,所以不用担心遗忘了释放导致的错误

自动初始化:不用把 auto_ptr 初始化为 NULL,因为有默认的构造函数帮你做了这件事情

防止野指针:普通的指针的一个通病就是担心产生野指针,即指针指向一个已经释放的对象。如下

<span style="font-family:Microsoft YaHei;">MyClass* p(new MyClass);MyClass* q = p;delete p;p->DoSomething();  //  p 现在已经是野指针p = NULL;  // p 现在不是野指针q -> DoSomething(); // q 是野指针</span>

然而,对于 auto_ptr ,复制的时候已经就被置空

<span style="font-family:Microsoft YaHei;">template<class T>auto_ptr<T>& auto_ptr<T>:: operator = (auto_ptr<T>& ths){ if(this != &rhs){ delete ptr;  ptr = rhs.ptr;  rhs.ptr = NULL;  }   return *this;}</span>


智能指针复制的时候,比如 q = p , 其中p 是智能指针,下面的一些操作也同时进行了:

1. 创建了p指向的对象的新的复制, 并让q 指向它。

2. 拥有转移: 让p 和 q 指向相同的对象,但是把清空的任务转移给了q。

3. 索引累加:维持指向对象的智能指针的数量统计,如果为0,则销毁对象。q =p 导致了

   引用计数的累加。

4. Reference linking: 类似引用计数累加维持一个统计,还有一个就是环形的双指针,链接起所有的指向

  相同对象的智能指针。

5. copy on write: 当智能指针指向的对象有发生改变,所有的copy  object都会发生改变。


二: STL 容器

C++ STL 具有广泛的使用性以及高效性。STL containers 通过存储值来存储对象。即如果你有STL containers 保存基类的对象

,那就不能保存基类派生的对象。

class  Base{/*...*/}class  Derived: public Base {/*...*/};Base b;Derived d;vector <Base> V;V.push_back(b); //正确V.push_back(d); // 错误

如果要收集不同类的对象,简单的方法是使用指针。

<span style="font-family:Microsoft YaHei;">vector<Base*> v;v.push_back(new Base);  // 正确v.push_back(new Derived);   //正确// cleanupfor(vector<Base*>::iterator i = v.begin(); i!= v.end(); ++i)    delete *i;</span>

这种方法需要手动的释放对象,当使用完 container

使用智能指针,则简单好多,也不容易出错

<span style="font-family:Microsoft YaHei;">vector<linked_ptr<Base>> V;v.push_back(new Base);v.push_back(new Derived);// cleanup is automatic</span>

要使用哪个?

1) 局部变量

auto_ptr 是最简单的标准智能指针,

2) 类的变量

虽然可以使用auto_ptr 作为类的成员(解决析构释放的问题), 类之间的复制也会自动置空。

<span style="font-family:Microsoft YaHei;">class MyClass{   auto_ptr<int> p;};MyClass x;//do some meaningful things with xMyclass y =x;  // x.p 现在也是个空指针</span>

智能指针使用引用计数以及双链表指针意味着 如果 y 改变了成员变量,这个改变也会影响到x。

三: STL containers

如上所述,使用自动垃圾回收的指针结合 STL containers  可以帮助你store 不同类的对象。



Summary

 对于:                                           使用:

Local variables                             auto_ptr

Class members                            Copied pointer



参考文献:

using C++11's Smarter pointers  , David Kieras, EECS Department, University of  Michigan

http://ootips.org/yonat/4dev/smart-pointers.html

0 0