shared_ptr:线程安全、循环引用

   shared_ptr用来管理堆对象可以避免delete,但是注意shared_ptr本身是个对象因此其的声明周期、shared_ptr对象的读写操作非原子化那么在多线程环境下仍然存在很多问题,即shared_ptr对象本身的线程安全级别非100%。在多线程中访问同一个shared_ptr对象应该用mutex保护。

   shared_ptr使用不当会延长锁管理的对象(假设为A)的生命周期,即有什么地方不小心留下一个shared_ptr的拷贝,那么对象A生命期无疑被延长了,因为对象计数不为0将不会被销毁。boost::bind会将函数参数拷贝一份,那么shared_ptr<T> one, boost::function<void()> fp=boost::bind(&fun,one)//这种情况会下one管理的对象生命期一定晚于fp对象。

   shared_ptr可能会导致循环引用即:对象A持有一个关于B的shared_ptr,B持有一个关于A的shared_ptr,那么A、B离开作用域的时候它们的计数都不为0(因为对方持有一个shared_ptr指向自己)而谁也不肯先释放这个shared_ptr,最后造成内存泄露。解决方法是:A持有关于B的shared_ptr,B持有关于A的weak_ptr。

    当一个管理对象A的shared_ptr离开作用域且计数变为0时那么对象A将在这个线程析构(这个线程可能不是创建A的线程),若析构比较费事,那么析构A的线程将被拖累,解决办法是:找个专门的线程管理所有的shared_ptr对象,并执行析构操作。

   我曾犯过的一个错误:若shared_ptr管理对象A,假设对象里面有new的堆内存,在对象A销毁前(即shared_ptr计数大于0)的时候,在某个地方A通过某些操作释放了A里里面的堆内存....那么最后的结果是?不错,double free,在A存活的时候释放了A里面的部分内存,那么在shared_ptr计数为0要销毁A的时候又去释放那块内存,然后就double free了。所以记住:避免shared_ptr管理的对象直接的内存管理操作以免造成重复释放

下面是多线程共享一个shared_ptr对象时的保护措施:

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
#include<boost/shared_ptr.hpp>
#include<boost/weak_ptr.hpp>
using namespace std;
using namespace boost;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//保护globalPtr的互斥量
class test{//shared_ptr管理的对象(假设为A)
    public:
        void show(string s){
            cout<<"test::show()  in: "<<s<<endl;
        }
};
shared_ptr<test> globalPtr(new test);

void fun(const shared_ptr<test>& ptr,string s){//用于展示多层函数传递shared_ptr时只用最外层函数持有shared_ptr对象实体,内层函数可以用const reference传递,毕竟大多数都是操作shared_ptr管理的对象而不是shared_ptr本身,const reference不会遇见返回拷贝shared_ptr对象导致的性能问题
    ptr->show(s);//操作shared_ptr管理的对象
}
void read(){//读globalPtr时候使用mutex保护
    shared_ptr<test> localptr;//减小临界区
    {
        pthread_mutex_lock(&mutex);
        localptr=globalPtr;//以后的操作只与localptr有关
        pthread_mutex_unlock(&mutex);
    }
    fun(localptr,"read()");
}
void write(){//重写globalPtr时使用mutex保护
    shared_ptr<test> newptr(new test);//重写globalPtr
    shared_ptr<test> lockptr;//###1
    {
        pthread_mutex_lock(&mutex);
        //globalPtr=newptr;//###2//这里与###1的区别是:直接使用本语句将有可能出现:globalPtr管理的对象A计数为1即这里将newptr赋给globalPtr后对象A将会被析构,则会使临界区变长,那么解决方式就是按照###1的那几句话
        lockptr.swap(globalPtr);//###1
        globalPtr=newptr;//###1
        pthread_mutex_unlock(&mutex);
    }
    fun(newptr,"write()");
    //fun(shared_ptr<test> one(new test),g()//构造一个临时的shared_ptr作为函数参数,函数参数求值顺序不一定,可能:new test第一,g()第二但是如果g()抛出一个异常,那么永远不可能到达shared_ptr的构造函数
}
void reset(){//销毁shared_ptr对象
    shared_ptr<test> localptr;//同样为了缩小临界区
    {
        pthread_mutex_lock(&mutex);
        localptr.swap(globalPtr);//globalPtr轻松就置空了且不会引起所管理的对象A在这里被析构,临界区非常小
        pthread_mutex_unlock(&mutex);
    }
    fun(localptr,"reset()");
}
void* worker(void* arg){
    int* x=(int*)arg;
    switch(*x){
        case 1:
            read();
            break;
        case 2:
            write();
            break;
        case 3:
            sleep(1);//pid3线程为了让其它两个线程执行完,不然这里使globalPtr置空了其它线程会报错
            reset();
            break;
        default:
            cout<<"are you kidding me"<<endl;
    }
}
int main(){
    pthread_t pid1,pid2,pid3;
    int i=1;
    pthread_create(&pid1,NULL,worker,&i);
    int j=2;//本来想用i++的但是所有线程都执行i=3的情形这说明:在前面两个线程还没有提取i=1,i=2的时候,线程3已经将i置为3了那么前两个线程就提取了i=3,这个错误以前犯过...哎...
    pthread_create(&pid2,NULL,worker,&j);
    int k=3;
    pthread_create(&pid3,NULL,&worker,&k);
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    pthread_join(pid3,NULL);
    return 0;
}

输出结果:

test::show()  in: write()test::show()  in: read()


test::show()  in: reset()


shared_ptr循环引用的例子:内存泄露问题

#include<iostream>
#include<boost/shared_ptr.hpp>
#include<boost/weak_ptr.hpp>
using namespace std;
using namespace boost;
class B;
class A{
    public:
        shared_ptr<B> ptr_A;
        ~A(){
            cout<<"~A()"<<endl;
        }
};
class B{
    public:
        //shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....
        weak_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了
        ~B(){
            cout<<"~B()"<<endl;
        }
};
int main(){
    shared_ptr<A> a(new A);
    shared_ptr<B> b(new B);
    a->ptr_A=b;
    b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了...
    return 0;
}
 
     若是循环引用则什么输出都没有即对象没有析构,内存泄露了。

     若是B持有A的weak_ptr则输出:

~A()
~B()

<think>好的,我现在需要解释一下C++中的std::shared_ptr和std::unique_ptr的区别,以及在什么情况下应该使用shared_ptr而不是unique_ptr。首先,我得回忆一下这两个智能指针的基本概念和用途。 我记得,std::unique_ptr是独占所有权的智能指针,也就是说,一个对象只能由一个unique_ptr拥有,不能复制,只能移动。这有助于避免资源泄露,因为当unique_ptr离开作用域时,它会自动释放所管理的对象。而std::shared_ptr则是共享所有权,多个shared_ptr可以指向同一个对象,内部通过引用计数来管理资源,当最后一个shared_ptr被销毁时,对象才会被释放。 接下来,我需要详细比较它们的区别。首先是所有权的模型,unique_ptr是独占的,而shared_ptr是共享的。然后是复制和移动的语义,unique_ptr不能复制,只能移动,而shared_ptr可以复制,引用计数会增加。性能方面,unique_ptr因为不需要维护引用计数,所以开销更小,而shared_ptr会有额外的开销,包括引用计数和可能的控制块的内存分配。 然后要考虑使用场景。unique_ptr适用于资源独占的情况,比如在工厂函数中返回对象,或者在对象需要单一所有者时。而shared_ptr则适用于需要多个对象共享同一个资源的情况,例如多个对象需要访问同一个数据结构,或者需要传递所有权到多个地方,比如在多线程环境中,多个线程可能需要共享某个资源。 另外,还需要提到循环引用的问题。shared_ptr可能导致循环引用,这时候需要使用std::weak_ptr来打破循环。而unique_ptr因为独占所有权,不会有这个问题。 用户可能还关心如何选择两者。通常优先使用unique_ptr,因为它更轻量,效率更高。只有在确实需要共享所有权的时候才使用shared_ptr。例如,当多个对象需要共同管理同一个资源的生命周期时,或者在需要将指针存入容器且多个地方需要持有该指针时。 可能还需要举个例子,比如一个对象的多个观察者需要访问该对象,这时候使用shared_ptr可以确保只要还有一个观察者存在,对象就不会被销毁。而unique_ptr适用于单一所有者的场景,比如某个资源只在某个作用域内使用,不需要传递所有权。 最后,要确保回答结构清晰,分点说明区别和使用场景,帮助用户逐步理解两者的不同和适用情况。</think>### std::shared_ptr 与 std::unique_ptr 的区别 #### 1. **所有权模型** - **std::unique_ptr** 独占所有权,同一时间只能有一个 `unique_ptr` 指向对象。 例如: ```cpp std::unique_ptr<int> ptr1 = std::make_unique<int>(42); // std::unique_ptr<int> ptr2 = ptr1; // 错误!不可复制 std::unique_ptr<int> ptr2 = std::move(ptr1); // 正确,所有权转移 ``` - **std::shared_ptr** 共享所有权,允许多个 `shared_ptr` 指向同一对象,通过引用计数管理生命周期。 例如: ```cpp std::shared_ptr<int> ptr1 = std::make_shared<int>(42); std::shared_ptr<int> ptr2 = ptr1; // 引用计数 +1 ``` --- #### 2. **性能差异** - **std::unique_ptr** 轻量级,无额外内存开销(仅包装原始指针)。 - **std::shared_ptr** 需要维护引用计数和控制块,存在额外内存和运行时开销。 --- #### 3. **使用场景对比** - **优先使用 std::unique_ptr 的情况** - 单一所有者场景(如工厂模式返回对象): ```cpp std::unique_ptr<Resource> createResource() { return std::make_unique<Resource>(); } ``` - 局部资源管理(如临时独占文件句柄)。 - **必须使用 std::shared_ptr 的情况** - **共享资源**:多个对象需共同管理同一资源。 例如:多个 GUI 组件共享同一数据模型。 - **跨作用域传递**:对象需在多个模块/线程中存活。 例如:线程池任务持有共享配置。 - **需要弱引用支持**:结合 `std::weak_ptr` 避免循环引用。 --- ### 何时选择 shared_ptr 而非 unique_ptr? 1. **需要共享所有权时** 当多个实体需同时访问且需要确保对象存活时。 ```cpp class Observer { public: void setTarget(std::shared_ptr<Target> target) { m_target = target; // 多个 Observer 共享同一 Target } private: std::shared_ptr<Target> m_target; }; ``` 2. **存在循环依赖时** 使用 `std::weak_ptr` 打破循环引用: ```cpp class Node { public: std::shared_ptr<Node> parent; std::weak_ptr<Node> child; // 弱引用避免循环 }; ``` 3. **需要延迟释放资源时** 例如:缓存系统允许多个用户临时持有资源。 --- ### 总结表格 | 特性 | std::unique_ptr | std::shared_ptr | |---------------------|--------------------------|--------------------------| | 所有权 | 独占 | 共享 | | 复制语义 | 禁止(只能移动) | 允许(引用计数 +1) | | 内存开销 | 无 | 控制块 + 引用计数 | | 典型场景 | 单一所有者、局部资源管理 | 共享资源、跨模块/线程使用| **优先选择 unique_ptr**:简单、高效、避免意外共享。 **仅当必须共享时** 才使用 shared_ptr,并注意潜在的性能和循环引用问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值