auto_ptr和shared_ptr

本文深入探讨了auto_ptr和shared_ptr的特性和使用方法,包括它们的局限性、构造与操作方式,以及如何避免常见错误。此外,还介绍了weak_ptr的作用及应用场景。

http://patmusing.blog.163.com/blog/static/13583496020101824541270/


a. auto_ptr定义于头文件memory中;

 

b. auto_ptr只能用来管理单个动态创建的对象,而不能管理动态创建的数组;

 

c. 和其他copyassign不同,auto_ptrcopyassign会改变右边的操作数,assignment符号的两边的auto_ptr均为左值;There is a crucially important difference between how auto_ptr and built-in pointers treat copy and assignment. When we

copy an auto_ptr or assign its value to another auto_ptr, ownership of the underlying object is transferred from the original

to the copy. The original auto_ptr is reset to an unbound state

 

d. auto_ptr不能作为容器中的元素;

  auto_ptrcopyassign具有析构行为,这就是auto_ptr不能作为容器元素的原因,因为标准库中的容器有对元素的要求:经

copy或者assign后的两个对象,必须相等;

 

e. 在判断一个auto_ptr是否被绑定的时候,不能直接使用auto_ptr对象:

      auto_ptr<Student> stu1(new Student);

         if(stu1)

         {

                   cout << "stu1 is bound" << endl;

         }

         else

         {

                   cout << "stu1 is unbound" << endl;

         }

         这样做将会导致compile error,应该改为:

         auto_ptr<Student> stu1(new Student);

         if(stu1.get())               // get()获取的是underlying对象的指针,如果被绑定则非零,如果没有被绑定则为0

         {

                   cout << "stu1 is bound" << endl;

         }

         else

         {

                   cout << "stu1 is unbound" << endl;

         }

 

f. auto_ptr的构造函数是explicit的,消除了隐式的类型转换(在这里即,从指针类型到auto_ptr类型的转换),因此不能直接将一个

指针赋给一个auto_ptr对象。如下面这样的代码:

         auto_ptr<Student> stu5 = new Student;

         stu5->printStudentInfo();

  在编译的时候不会有问题,但会出现严重的runtime error。正确的做法应该是:

         auto_ptr<Student> stu5(new Student);

         stu5->printStudentInfo();

 

g. 不同用两个auto_ptr绑定到同一个对象。

      // stu6stu7绑定到了同一个对象,这将会导致该对象被析构两次,将会产生runtime error

         auto_ptr<Student> stu6(new Student("Evanligine""F", 8));

         auto_ptr<Student> stu7(stu6.get());

         后面一句,应该改为:

         auto_ptr<Student> stu7(stu6);

         这样stu6就将ownership转交给了stu7stu6则成为了unboundauto_ptr            

 

h. 不能用auto_ptr指向静态资源分配对象。如下面的代码,尽管可以通过编译,但将会产生runtime error

         int ix = 10;

         auto_ptr<int> pint1(&ix);

 

i. auto_ptr的重要操作

auto_ptr<T> ap;               创建一个未绑定的auto_ptr对象ap

auto_ptr<T> ap(p);           创建一个auto_ptr对象ap,它绑定了指针p所指向的对象。该构造函数是explicit

auto_ptr<T> ap1(ap2);      创建一个auto_ptr对象ap1,它绑定到原来被ap2绑定的对象,ap2则成为未绑定的auto_ptr

ap1 = ap2;                       ap1删除原来绑定的对象,ap2ownership移交给ap1ap2成为未绑定的auto_ptr

*ap                                  返回ap绑定的对象的引用。可以通过*给被绑定的内在对象赋值。如下面代码:

                                                        auto_ptr<int> pint(new int(3));

                                                        cout << *pint << endl;                           // 输出3

                                                        *pint = 100;

                                                        cout << *pint << endl;                           // 输出100

ap->                                返回被ap绑定的对象的指针

ap.reset(p)                       如果指针pap绑定的内存对象的指针不相同,那么ap删除被其绑定的内存对象,改而绑定p

         指向的对象.

ap.release()                    ap内部指向为NULL,但是原来所指内存并不会主动释放,要手动去释放调用delete

ap.get()                           返回ap所绑定对象的指针


#include <iostream>
#include <memory>

using namespace std;

class A {
    public:
        int a;
        A(int aa):a(aa) {
            cout << "constructing A = " << a<<endl;
        }

        ~A() {
            cout << "deconstruct A = " << a <<endl;
        }

        void fun() {
            cout << "print A = " << a <<endl;
        }
};

int main() {
    {
    auto_ptr<A> p1(new A(1));
    if(p1.get()) {
        p1.get()->a = 2;
        (*p1.get()).a = 3;
        p1.get()->fun();

        (*p1).a = 4;//*p1就是得到了原对象的引用
        (*p1).fun();
        p1->a = 5;//p1->返回的是原对象的指针
        p1->fun();
        
        auto_ptr<A> p2 = p1;
        cout << p1.get()<<endl;
        p2->fun();
        //p2.release();//比较危险,只是p2指向NULL,但是原来的内存不释放,即使出了这个作用域
        //cout << "releas p2\n";
        //cout << p2.get()<<endl;
        p2.reset(new A(12));
        p2.reset();//主动释放内存
        cout << "bounded\n";
    }
    else{
        cout << "unbound\n";
    }
    }

    cout << "out of area\n";
    return 0;
}

1、auto_ptr不能共享所有权。
2、auto_ptr不能指向数组
3、auto_ptr不能作为容器的成员。
4、不能通过赋值操作来初始化auto_ptr
std::auto_ptr<int> p(new int(42));     //OK
std::auto_ptr<int> p = new int(42);    //ERROR
这是因为auto_ptr 的构造函数被定义为了explicit
5、不要把auto_ptr放入容器

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

对于shared_ptr先看个例子


class A {
    public:
        int a;
        A(int aa):a(aa) {
            cout << "constructing A = " << a<<endl;
        }

        ~A() {
            cout << "deconstruct A = " << a <<endl;
        }

        void fun() {
            cout << "print A = " << a <<endl;
        }

        tr1::shared_ptr<A> m_other;
};

int main() {
    
        
    {
        
        tr1::shared_ptr<A> p1(new A(1));

        tr1::shared_ptr<A> p2(new A(2));
        //p1->m_other = p2;//p2->m_other = p1;
    }
}
如果将最后一句注释,释放的顺序是先2后1,p1和p2都是栈上定义的,所以先释放2再释放1

如果是p1->m_other = p2;

先要释放p2的资源,但是p2被引用了两次,所以释放只是把引用次数变为1

在释放p1的资源,先进入析构函数,释放p1,在析构p1的内部成员变量,就是p2,这时p2发现自己的引用次数为1了,再调用p2的析构


如果是p2->m_other = p1

先进入p2的析构函数,再去析构p2的成员变量m_other,发现其引用次数为2,所以只是讲其引用次数变为1

在进入p1的析构函数


循环引用

class B;

class A {
    public:
        int a;
        A(int aa):a(aa) {
            cout << "constructing A = " << a<<endl;
        }

        ~A() {
            cout << "deconstruct A = " << a <<endl;
        }

        void fun() {
            cout << "print A = " << a <<endl;
        }

        tr1::shared_ptr<B> m_other;
};

class B {
    public:
        int b;
        B(int bb):b(bb) {
            cout << "constructing B = " << b<<endl;
        }

        ~B() {
            cout << "deconstruct B = " << b <<endl;
        }

        void fun() {
            cout << "print B = " << b <<endl;
        }

        tr1::shared_ptr<A> m_other;
};


int main() {
    
        tr1::weak_ptr<A> wp;
    {
        
        tr1::shared_ptr<A> pa(new A(1));

        tr1::shared_ptr<B> pb(new B(2));
        pb->m_other = pa;
        pa->m_other = pb;
    }
}

output:

constructing A = 1
constructing B = 2
bounded
out of area


都没进入析构函数释放资源

先释放pb,发现pb指向的元素被引用两次,所以只是将引用次数变为1

后释放pa,同理,将引用次数变为1


这里就有了内存泄露


需要用weak_ptr打破循环引用

把class A 或者B中任何一个或者两个m_other变为weak_ptr就可以解决上述问题

weak_ptr

weak_ptr本身不具有指针的行为,例如你不能对一个weak_ptr来进行*或者->操作。它通常用来和shared_ptr配合使用。

weak_ptr作为一个”shared_ptr的观察者”能够获知shared_ptr的引用计数,还可以获知一个shared_ptr是否已经被析构了。单冲这一点来说,就一点不weak了


构造weak_ptr

有两种方法可以构造一个weak_ptr

1、 从shared_ptr构造而来。这种情况不会增加shared_ptr的引用计数。当然会增加另一个计数,这个放到下一篇中讲。

2、 从另一个weak_ptr拷贝。

也就是说weak_ptr不可能脱离shared_ptr而存在。


expired()

返回布尔,当返回true的时候表示,weak_ptr关联的shared_ptr已经被析构了。

int _tmain(int argc, _TCHAR* argv[])

{

shared_ptr<foo> fptr=shared_ptr<foo>(new foo(1,2));

weak_ptr<foo> wptr=fptr;

fptr.reset();

if(wptr.expired())

{

cout<<”wptr has expired”<<endl;

}

system(“pause”);

return 0;

}

lock()

从当前的weak_ptr创建一个新的shared_ptr。如果此时expired()返回true时,创建的shared_ptr中将保存一个null_ptr。

use_count()

返回当前关联的shared_ptr的引用计数是多少。expired()返回true时,该函数返回0。

weak_ptr使用场景

weak_ptr的特性是:weak_ptr不会增加shared_ptr的引用计数,所以weak_ptr通常用来解决shared_ptr无法解决的问题,例如环形引用。weak_ptr常见的使用场景有这么几个:

1、 想管理某些资源,但是又不想增加引用计数,那么就可以保存weak_ptr。

2、 当知道了有环形引用后,可以使用weak_ptr。例如上面的例子可以改为这样:

class CParent

{

public:

shared_ptr< CChild > children;

};

class CChild

{

public:

weak_ptr< CParent > parent;

};

int main()

{

{

shared_ptr< CParent > pA(new CParent);

shared_ptr< CChild > pB(new CChild);

pA-> children =pB;

pB-> parent =pA;

}

}

3、 某些情况下,需要知道某个shared_ptr是否已经释放了。


总结

1、 在遗留代码上如果要引入shared_ptr要谨慎!shared_ptr带来的不确定性可能要比带来的便利性大的多。

2、 使用shared_ptr并不是意味着能偷懒。反而你更需要了解用shared_ptr管理的对象的生命周期应该是什么样子的,是不是有环形引用,是不是有线程安全问题,是不是会在某个地方意外的被某个东西hold住了。

3、 一个对象如何使用shared_ptr管理那么最好全部使用shared_ptr来管理,必要的时候可以使用weak_ptr。千万不要raw ptr和智能指针混用

4、 多线程读写同一个shared_ptr的时候,可以先加锁拷贝一份出来,然后解锁即可。

http://www.sssa2000.com/?p=915




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值