shared_ptr的 deleter, weak_ptr.lock 在多线程的运用举例

本文介绍如何利用shared_ptr和weak_ptr管理对象池中的Stock实例,通过deleter确保Stock实例及其在对象池中的记录同步销毁,避免内存泄露。

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

1.shared_ptr的删除器(deleter)

可以在 shared_ptr 的构造函数 和 reset 函数里面给 shared_ptr 加入 deleter。

定义:

 template<class Y, class D> shared_ptr::shared_ptr(Y* p, D d);


 template<class Y, class D> void shared_ptr::reset(Y* p, D d);

 

deleter的作用是:deleter可以是一个函数或者是仿函数。在调用 shared_ptr 的析构函数的时候会有一个 delete 操作,现在提供的这个 deleter,就是用来替代delete的。

会执行:d(p)

ex:

shared_ptr<int> tem(new int, delFun);

那么在调用 shared_ptr的析构函数的时候 delete p 会被替换为 delFun(p)

 

2.weak_ptr的lock函数:

weak_ptr.lock() 会把weak_ptr提升为一个 shared_ptr 对象,当 引用计数为0 ,那么 shared_ptr 为空。

ex:

shared_ptr<int> shPtr = weakPtr.lock();

if(! shPtr)

{

    //进入这里,表示引用计数为0;

    return;

}

//在这里,说明引用计数不为0,成功的构造了一个 shared_ptr

 

3.下面一个例子来说明deleter和 weak.lock() 的使用

 

假设有 Stock 类,代表一只股票的价格。每一只股票有一个惟一的字符串标识,比如 Google 的 key 是 "NASDAQ:GOOG",IBM 是 "NYSE:IBM"。Stock 对象是个主动对象,它能不断获取新价格。为了节省系统资源,同一个程序里边每一只出现的股票只有一个 Stock 对象,如果多处用到同一只股票,那么 Stock 对象应该被共享。

为了达到上述要求,我们可以设计一个对象池 StockFactory。它的接口很简单,根据 key 返回 Stock 对象。如果stock对象不存在就创建一个。

(1)为了管理这个对象池StockFactory, 我们最初的设计如下:

class StockFactory : boost::noncopyable    

{ // questionable code  

 public:  

  shared_ptr<Stock> get(const string& key);

 private:  

  std::map<string, shared_ptr<Stock> > stocks_;  

  mutable Mutex mutex_;  

};

这里有一个问题,shared_ptr<Stock> 使得stock的生命周期被延长了。stock的生命周期与 StockFactory一样长了。

 

(2)我们知道weak_ptr不会延长对象的生命周期, weak_ptr只是对象一个observer。于是我们有下面的设计:

class StockFactory : boost::noncopyable 

 public: 
  shared_ptr<Stock> get(const string& key) 
  { 
    shared_ptr<Stock> pStock; 
    MutexLock lock(mutex_); 
    weak_ptr<Stock>& wkStock = stocks_[key];  // 如果 key 不存在,会默认构造一个 
    pStock = wkStock.lock();  // 尝试把weak_ptr提升为shared_ptr

    if (!pStock) { 
      pStock.reset(new Stock(key)); 
      wkStock = pStock;  // 这里更新了 stocks_[key],注意 wkStock 是个引用 
    } 
    return pStock; 
  }    

 private: 

  std::map<string, weak_ptr<Stock> > stocks_; 
  mutable Mutex mutex_; 

};

 

(3).上面的代码解决了延长对象生命周期的问题,但是仍然有问题,那就是即使对象已经不存在了,它仍然在 stocks_ 里面存在。 这样也可以叫做内存泄露。

为了解决这个问题,我们现在就要用到 shared_ptr 的 deleter 功能:这里deleter保证在删除stock的同时也删除 这个stock在 stocks_ 里面的存在。

class StockFactory : public boost::enable_shared_from_this<StockFactory>,boost::noncopyable 

 public: 
  shared_ptr<Stock> get(const string& key) 
  { 
    shared_ptr<Stock> pStock; 
    MutexLock lock(mutex_); 
   
    weak_ptr<Stock>& wkStock = stocks_[key]; 
    pStock = wkStock.lock(); 

    if (!pStock) { 
      pStock.reset(new Stock(key), 
                    boost::bind(&StockFactory::weakDeleteCallback, 
                                 boost::weak_ptr<StockFactory>(shared_from_this()), 
                                 _1)); 
      // 上面必须强制把 shared_from_this() 转型为 weak_ptr,才不会延长生命期 

      wkStock = pStock; 
    } 
    return pStock; 
  } 

 private: 
  static void weakDeleteCallback(boost::weak_ptr<StockFactory> wkFactory, Stock* stock)
  { 
    shared_ptr<StockFactory> factory(wkFactory.lock());  // 尝试提升 
    if (factory) {  // 如果 factory 还在,那就清理 stocks_ 
      factory->removeStock(stock); 
    } 
    delete stock;  //
  }    

  void removeStock(Stock* stock) 
  { 
    if (stock) { 
      MutexLock lock(mutex_); 
      stocks_.erase(stock->key()); 
    } 
  } 
 
 private: 
  std::map<string, weak_ptr<Stock> > stocks_;
  mutable Mutex mutex_; 
};

 

 

 

 

 

 

 

 

 

<think>我们正在讨论C++中的四种智能指针:shared_ptr, unique_ptr, weak_ptr, 以及auto_ptr(注意auto_ptr已被弃用)。我将根据用户提供的引用和我的知识来详细解释它们的用途和区别。 首先,注意auto_ptr在C++11中已被弃用,在C++17中被移除,因此不推荐使用。我们将重点讨论其他三种。 1. **shared_ptr**: - 用途:共享所有权。多个shared_ptr可以指向同一个对象,通过引用计数来管理对象的生命周期。当最后一个shared_ptr被销毁时,对象会被删除。 - 特点: - 引用计数是原子的,因此线程安全(但指向的对象本身不是线程安全的)。 - 避免使用裸指针初始化多个shared_ptr,否则会导致双重释放(如引用[2]和[4]所述)。 - 推荐使用`std::make_shared`来创建,因为只分配一次内存(同时存储对象和控制块),效率更高(引用[4])。 - 不要用栈上的指针构造(如引用[1]所示),因为栈上的对象会在作用域结束时自动销毁,而shared_ptr会试图delete它,导致未定义行为。 2. **unique_ptr**: - 用途:独占所有权。同一时间只能有一个unique_ptr指向一个对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。 - 特点: - 轻量级,没有引用计数的开销。 - 不可复制,但可以移动(使用std::move)。 - 适用于资源独占的场景,比如在函数内部动态分配的对象,或者作为工厂函数的返回值。 - 可以自定义删除器(deleter),用于释放资源(比如文件句柄,使用fclose)。 3. **weak_ptr**: - 用途:解决shared_ptr的循环引用问题。weak_ptr指向一个由shared_ptr管理的对象,但不增加引用计数。因此,它不会影响对象的生命周期。 - 特点: - 不能直接访问对象,必须通过lock()方法转换成shared_ptr来使用(引用[3])。 - 用于观察对象,而不拥有对象。例如,在观察者模式中,观察者持有被观察者的weak_ptr,避免循环引用。 - 使用expired()或use_count()可以检查对象是否还存在。 4. **auto_ptr** (已弃用): - 用途:在C++98中引入,用于独占所有权,但在C++11中被unique_ptr取代。 - 特点: - 存在所有权转移的问题:当复制一个auto_ptr时,原auto_ptr会失去所有权(变为nullptr),这容易导致意外。 - 因此,C++11中引入了unique_ptr,它通过禁止复制(允许移动)来更安全地实现独占所有权。 **使用场景总结**: - 需要共享所有权:使用`shared_ptr`(例如,多个对象需要共享同一个资源)。 - 需要独占所有权:使用`unique_ptr`(例如,在函数中动态分配对象,并在函数结束时自动释放)。 - 需要打破循环引用:使用`weak_ptr`(例如,在双向链表、树结构或观察者模式中)。 - 避免使用`auto_ptr`。 **注意事项**: - 避免使用裸指针初始化智能指针,特别是同一个裸指针初始化多个智能指针(会导致多次释放)。 - 优先使用`make_shared`和`make_unique`(C++14引入)来创建智能指针,因为它们更安全(避免裸指针泄露)且更高效(减少内存分配次数)。 - 不要将栈上对象的地址给智能指针(智能指针默认使用delete释放,而栈对象会自动销毁)。 下面用代码示例说明: ```cpp #include <memory> #include <iostream> class MyClass { public: MyClass() { std::cout << "MyClass constructed\n"; } ~MyClass() { std::cout << "MyClass destroyed\n"; } }; void sharedPtrExample() { std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(); // 引用计数=1 { std::shared_ptr<MyClass> sp2 = sp1; // 引用计数=2 } // sp2销毁,引用计数=1 // sp1销毁,引用计数=0,对象销毁 } void uniquePtrExample() { std::unique_ptr<MyClass> up(new MyClass()); // 独占所有权 // std::unique_ptr<MyClass> up2 = up; // 错误,不能复制 std::unique_ptr<MyClass> up2 = std::move(up); // 可以移动,现在up为空 // 当up2离开作用域,对象销毁 } void weakPtrExample() { std::shared_ptr<MyClass> sp = std::make_shared<MyClass>(); std::weak_ptr<MyClass> wp = sp; // 不增加引用计数 if (auto locked = wp.lock()) { // 尝试提升为shared_ptr // 对象还存在,可以使用 } else { // 对象已被释放 } } int main() { sharedPtrExample(); uniquePtrExample(); weakPtrExample(); return 0; } ``` **输出**(大致顺序): ``` MyClass constructed MyClass destroyed // sharedPtrExample中sp1销毁时 MyClass constructed // uniquePtrExample中创建 MyClass destroyed // uniquePtrExample中up2销毁时 MyClass constructed // weakPtrExample中创建 MyClass destroyed // weakPtrExample中sp销毁时 ``` **引用说明**: - 引用[1]和[2]提醒我们不要用栈上的指针初始化shared_ptr,以及避免混合使用原始指针和shared_ptr。 - 引用[3]解释了weak_ptr的作用和用法。 - 引用[4]说明了shared_ptr的创建方式和线程安全性。 最后,关于auto_ptr,由于已被弃用,我们只需知道它被unique_ptr取代即可。 §§相关问题§§ 1. 如何避免shared_ptr的循环引用问题? 2. make_shared和直接使用new创建shared_ptr有什么区别? 3. unique_ptr如何作为函数返回值传递? 4. weak_ptrlock()方法在什么情况下返回空的shared_ptr? 5. 智能指针在多线程环境下的安全性如何?</think>### C++ 智能指针详解:shared_ptr, unique_ptr, weak_ptr, auto_ptr #### 1. **shared_ptr(共享指针)** - **核心机制**:基于引用计数的共享所有权模型 - **特点**: - 多个指针可共享同一对象 - 引用计数为0时自动释放资源 - 线程安全的引用计数操作(原子操作)[^4] - **创建方式**: ```cpp // 推荐方式(单次内存分配) auto sp1 = std::make_shared<int>(10); // 传统方式(两次内存分配) std::shared_ptr<int> sp2(new int(20)); ``` - **使用场景**: - 需要多个对象共享同一资源 - 需要传递对象所有权但不确定何时释放 - 避免使用裸指针初始化多个shared_ptr(防止双重释放)[^2] - **注意事项**: - 禁止用栈指针初始化(会导致双重释放)[^1] ```cpp int x = 12; // std::shared_ptr<int> ptr(&x); // 严重错误! ``` #### 2. **unique_ptr(独占指针)** - **核心机制**:独占所有权模型(不可复制) - **特点**: - 零运行时开销(无引用计数) - 支持移动语义(`std::move`) - 可定制删除器(如文件句柄、自定义释放) - **创建方式**: ```cpp auto up = std::make_unique<std::string>("Hello"); ``` - **使用场景**: - 独占资源所有权的场景(如工厂模式返回值) - 替代已弃用的auto_ptr - 需要明确资源所有权的转移 - **所有权转移**: ```cpp auto src = std::make_unique<int>(42); auto dest = std::move(src); // src变为nullptr ``` #### 3. **weak_ptr(弱引用指针)** - **核心机制**:观察者模式(不增加引用计数) - **特点**: - 不拥有资源所有权 - 解决shared_ptr循环引用问题 - 需通过`lock()`转为shared_ptr访问资源[^3] - **使用场景**: ```cpp auto shared = std::make_shared<Resource>(); std::weak_ptr<Resource> observer = shared; if (auto locked = observer.lock()) { // 检查资源有效性 locked->use(); // 安全使用资源 } ``` - **关键方法**: - `expired()`:检查资源是否已被释放 - `use_count()`:获取当前引用计数 #### 4. **auto_ptr(已弃用)** - **历史地位**:C++98引入的独占指针 - **致命缺陷**: - 复制操作导致所有权静默转移 ```cpp auto_ptr<int> ap1(new int(5)); auto_ptr<int> ap2 = ap1; // ap1变为nullptr! ``` - **现状**: - C++11起被标记为deprecated - C++17中完全移除 - 必须用unique_ptr替代 #### 对比总结 | 特性 | shared_ptr | unique_ptr | weak_ptr | auto_ptr(弃用) | |---------------------|------------------|------------------|------------------|-----------------| | **所有权** | 共享 | 独占 | 无 | 独占 | | **复制语义** | ✅(增加计数) | ❌(仅移动) | ✅(不增计数) | ❌(转移所有权) | | **线程安全** | 引用计数原子操作 | 非线程安全 | 依赖shared_ptr | 非线程安全 | | **循环引用处理** | 需配合weak_ptr | 不适用 | 解决方案 | 不适用 | | **性能开销** | 中(控制块) | 低(无额外开销) | 低 | 低 | | **推荐使用场景** | 共享资源 | 独占资源 | 打破循环引用 | 禁止使用 | #### 最佳实践指南 1. **默认选择unique_ptr** ```cpp auto resource = std::make_unique<DatabaseConnection>(); ``` 2. **共享资源用shared_ptr+make_shared** ```cpp auto config = std::make_shared<GlobalConfig>(); ``` 3. **存在循环引用时添加weak_ptr** ```cpp class Node { std::shared_ptr<Node> parent; std::vector<std::weak_ptr<Node>> children; // 弱引用打破循环 }; ``` 4. **绝对避免auto_ptr** ```cpp // std::auto_ptr<int> obsolete; // 已移除,编译错误 ``` > 智能指针的核心价值:**自动化资源生命周期管理**,消除内存泄漏和悬空指针风险[^1][^3][^4]。现代C++开发中,应优先使用make_shared/make_unique(C++14起)创建智能指针,既保证异常安全又提升性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值