shared_ptr三个陷阱

本文介绍了shared_ptr的正确使用方法,包括避免多次引用同一原始指针、正确包装this指针及多线程环境下如何确保线程安全。通过示例对比了正确的实现方式与常见错误做法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

shared_ptr 存储一个指向动态分配对象的指针。在最后一个 shared_ptr 所指向的对象被销毁或重置时,要保证它所指向的对象被删除

1. shared_ptr不能多次引用同一原始指针

错误:当p1,p2离开作用域时,由于p1,p2的引用计数都为0,会导致两次释放同一块内存
#include <iostream>
#include <tr1/memory>
using std::tr1::shared_ptr;
using std::cout;
using std::endl;

int main()
{
        int *tmp = new int(10);
        shared_ptr<int> p1(tmp);
        cout << "the ref count of p1: " << p1.use_count() << endl;
        shared_ptr<int> p2(tmp);
        cout << "the ref count of p1: " << p1.use_count() << "\t the ref count of p2: " << p2.use_count() << endl;

        return 0;
}

output:

the ref count of p1: 1
the ref count of p1: 1 the ref count of p2: 1
*** glibc detected *** ./main: double free or corruption (fasttop): 0x0000000000f97010 ***
----------------------------------------------------
正确:使用最初的shared_ptr实例拷贝来维护对象生存期
#include <iostream>
#include <tr1/memory>
using std::tr1::shared_ptr;
using std::cout;
using std::endl;

int main()
{
        shared_ptr<int> p1(new int(10));
        cout << "the ref count of p1: " << p1.use_count() << endl;
        shared_ptr<int> p2(p1);
        cout << "the ref count of p1: " << p1.use_count() << "\t the ref count of p2: " << p2.use_count() << endl;

        return 0;
}

output:

the ref count of p1: 1
the ref count of p1: 2 the ref count of p2: 2

2. shared_ptr不能包装this

错误:直接包装this
#include <iostream>
#include <tr1/memory>
using std::tr1::shared_ptr;
using std::cout;
using std::endl;

class foo
{
public:
        shared_ptr<foo> get()
        {
                return shared_ptr<foo>(this);
        }
};

int main()
{
        shared_ptr<foo> p1(new foo);
        cout << "the ref count of p1: " << p1.use_count() << endl;
        shared_ptr<foo> p2 = p1->get();
        cout << "the ref count of p1: " << p1.use_count() << "\t the ref count of p2: " << p2.use_count() << endl;

        return 0;
}

output:

the ref count of p1: 1
the ref count of p1: 1 the ref count of p2: 1
*** glibc detected *** ./main: double free or corruption (fasttop): 0x00000000021e1010 ***

正确:使用shared_from_this

shared_from_this()是enable_shared_from_this<T>的成员函数,返回shared_ptr<T>
enable_shared_from_this::weak_ptr并不在构造函数中设置,而是在shared_ptr<T>的构造函数中设置,
所以,shared_from_this()必须在shared_ptr<T>的构造函数被调用之后才能使用

Every enable_shared_from_this base contains a weak_ptr, 
the shared_ptr constructor looks up the enable_shared_from_this base and initializes its weak_ptr accordingly. 
This doesn't work when there are two or more enable_shared_from_this bases, though. 

#include <iostream>
#include <tr1/memory>
using std::tr1::shared_ptr;
using std::cout;
using std::endl;

class foo : public std::tr1::enable_shared_from_this<foo>
{
public:
        shared_ptr<foo> get()
        {
                return shared_from_this();
        }
};

int main()
{
        shared_ptr<foo> p1(new foo);
        cout << "the ref count of p1: " << p1.use_count() << endl;
        shared_ptr<foo> p2 = p1->get();
        cout << "the ref count of p1: " << p1.use_count() << "\t the ref count of p2: " << p2.use_count() << endl;

        return 0;
}

output:

the ref count of p1: 1
the ref count of p1: 2 the ref count of p2: 2

执行shared_ptr<foo> p1(new foo)做了3个动作,首先,调用enable_shared_from_this的构造函数;其次,调用foo的构造函数;最后,调用shared_ptr的构造函数

3.多线程对同一个share_ptr执行写操作是不安全的,共享引用计数的不同的shared_ptr被多线程写是线程安全的
共享引用计数如下:

include <iostream>
#include <tr1/memory>
#include <pthread.h>
using std::tr1::shared_ptr;
using std::cout;
using std::endl;

shared_ptr<int> spKey(new int(9999));

shared_ptr<int> doSomething(shared_ptr<int> &param)
{
        shared_ptr<int> sp(param); // sp and param share the same ref count
        // do something to change sp
        shared_ptr<int> tmp(new int(0));
        //cout << "before swap, sp ref count: " << sp.use_count() << "\t\ttmp ref count" << tmp.use_count() << endl;
        sp.swap(tmp);
        tmp.swap(sp);
        //cout << "after swap, sp ref count: " << sp.use_count() << "\t\ttmp ref count" << tmp.use_count() << endl;

        return sp;
}

void *run(void *param)
{
        for (int i = 0; i < 10000; ++i)
        {
                shared_ptr<int> tmp = doSomething(spKey);
                if (9999 != *tmp)
                {
                        cout << "not safe......" << endl;
                        break;
                }
        }
        cout << "quit....." << endl;

        return NULL;
}

int main()
{
        int i, num = 5;
        pthread_t threads[5];
        for (i = 0; i < num; ++i)
                pthread_create(&threads[i], 0, run, NULL);
        for (i = 0; i < num; ++i)
                pthread_join(threads[i], 0);

        return 0;

}

output:

g++ shared_ptr-test.cpp -o main -pthread
./main 

quit.....
quit.....
quit.....
quit.....
quit.....



<think>好的,我现在需要详细介绍一下C++中的weak_ptr。首先,我需要回忆一下用户之前的提问历史。用户之前询问了智能指针的总体介绍,特别是提到了unique_ptrshared_ptr和weak_ptr,现在用户希望更详细地了解weak_ptr。 首先,我应该明确weak_ptr的基本概念。weak_ptr是一种不控制对象生命周期的智能指针,它指向由shared_ptr管理的对象。它的存在主要是为了解决shared_ptr的循环引用问题,这种问题会导致内存泄漏。 接下来,我需要解释weak_ptr的工作原理。因为它不拥有对象的所有权,所以不会增加引用计数。当最后一个shared_ptr释放对象时,即使还有weak_ptr指向该对象,对象也会被销毁。这时候weak_ptr会变成空指针,无法直接访问对象。 然后,我需要说明如何创建和使用weak_ptr。通常,weak_ptr是通过shared_ptr来构造的。例如,使用shared_ptr的实例来初始化weak_ptr。同时,访问weak_ptr指向的对象需要使用lock()方法,这会返回一个shared_ptr,这样可以确保在使用对象时,对象仍然存在。 此外,应该提到weak_ptr的主要用途。除了解决循环引用之外,weak_ptr还可以用于缓存、观察者模式等场景,其中不需要拥有对象的所有权,但需要安全地访问可能已被销毁的对象。 需要详细说明循环引用的问题。例如,两个类互相持有对方的shared_ptr,导致引用计数无法归零,从而内存无法释放。而使用weak_ptr可以打破这种循环,因为它不会增加引用计数。 还应该讨论weak_ptr的常用方法,比如expired()用于检查对象是否已被释放,reset()来释放资源,以及use_count()查看当前shared_ptr的引用计数。 可能还需要比较weak_ptr与其他智能指针的不同之处,特别是与shared_ptr和unique_ptr的区别。强调weak_ptr不能单独使用,必须与shared_ptr配合使用。 最后,给出一些使用示例,展示如何正确使用weak_ptr,例如在观察者模式中的典型应用,或者如何通过lock()安全地访问对象。 需要确保解释清晰,避免技术术语过多,但又要覆盖关键点。同时,注意结构合理,分点说明,便于用户理解。可能还需要提醒用户常见的陷阱,比如直接解引用weak_ptr会导致未定义行为,必须通过lock()获取shared_ptr后才能访问数据。 总结一下,我需要涵盖以下内容: 1. weak_ptr的定义和作用 2. 解决循环引用的机制 3. 创建和使用方法,包括lock()、expired()等 4. 使用场景和示例 5. 注意事项和最佳实践 这样用户能全面了解weak_ptr的用途和正确使用方法。</think>`weak_ptr` 是 C++ 中与 `shared_ptr` 配合使用的智能指针,**专为打破 `shared_ptr` 的循环引用问题而设计**。它不拥有对象的所有权,也不影响引用计数,而是以“观察者”身份安全地访问 `shared_ptr` 管理的资源。以下从核心机制、使用方法和典型场景详细说明: --- ### 一、核心机制 #### 1. **不持有所有权** - **不增加引用计数**:`weak_ptr` 指向 `shared_ptr` 管理的对象时,不会增加其引用计数。 - **资源可被释放**:当最后一个 `shared_ptr` 释放对象后,对象内存会被销毁,即使仍有 `weak_ptr` 指向它。 #### 2. **解决循环引用** - **循环引用问题**:若两个对象互相持有 `shared_ptr`,引用计数永远无法归零,导致内存泄漏。 - **`weak_ptr` 的解决方式**:将其中一个 `shared_ptr` 替换为 `weak_ptr`,打破循环依赖,允许引用计数归零。 #### 3. **访问数据的条件** - **必须通过 `lock()`**:需调用 `weak_ptr::lock()` 返回一个临时 `shared_ptr`,若对象存活则引用计数 +1,否则返回空。 --- ### 二、核心操作与成员函数 #### 1. **构造与赋值** - **从 `shared_ptr` 构造**:只能通过 `shared_ptr` 或另一个 `weak_ptr` 初始化。 ```cpp auto shared = std::make_shared<int>(42); std::weak_ptr<int> weak1(shared); // 直接构造 std::weak_ptr<int> weak2 = shared; // 隐式转换 ``` #### 2. **检查与访问** - **`expired()`**:快速检查对象是否已被释放(引用计数是否为 0)。 ```cpp if (weak.expired()) { /* 对象已销毁 */ } ``` - **`lock()`**:获取临时 `shared_ptr`,安全访问对象。 ```cpp if (auto temp = weak.lock()) { // 对象存活,temp 是有效的 shared_ptr std::cout << *temp << std::endl; } ``` #### 3. **其他操作** - **`reset()`**:断开 `weak_ptr` 与对象的关联(置空)。 - **`use_count()`**:返回关联的 `shared_ptr` 的引用计数(调试用,避免依赖此值)。 --- ### 三、典型使用场景 #### 1. **打破循环引用** ```cpp class B; // 前向声明 class A { public: std::shared_ptr<B> b_ptr; ~A() { std::cout << "A destroyed\n"; } }; class B { public: std::weak_ptr<A> a_weak; // 使用 weak_ptr 替代 shared_ptr ~B() { std::cout << "B destroyed\n"; } }; int main() { auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; b->a_weak = a; // 不增加 A 的引用计数 // a 和 b 的引用计数均归零,正确析构 } ``` #### 2. **缓存与观察者模式** - **缓存场景**:缓存对象时,使用 `weak_ptr` 持有资源,若对象已被释放则重新加载。 - **观察者模式**:观察者通过 `weak_ptr` 观察主体对象,避免因主体提前释放导致的悬空指针。 --- ### 四、注意事项 1. **禁止直接解引用** `weak_ptr` 没有重载 `operator*` 或 `operator->`,必须通过 `lock()` 获取 `shared_ptr` 后访问数据。 ```cpp // 错误!无法直接访问 // int value = *weak_ptr; // 正确方式 if (auto sp = weak_ptr.lock()) { int value = *sp; } ``` 2. **`lock()` 的原子性** `lock()` 的调用是线程安全的,但返回的 `shared_ptr` 可能仍需其他同步机制保护数据。 3. **性能开销** `weak_ptr` 内部需维护控制块,但无原子计数操作,开销通常低于 `shared_ptr`。 --- ### 五、总结 `weak_ptr` 是 `shared_ptr` 的“助手”,通过以下特性优化内存管理: - **安全性**:避免循环引用导致的内存泄漏。 - **灵活性**:在不拥有所有权的前提下观察资源状态。 - **轻量化**:无额外引用计数开销。 正确使用 `weak_ptr` 可显著提升代码的健壮性,尤其在复杂对象关系或资源缓存场景中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值