C++智能指针:shared_ptr、unique_ptr、weak_ptr区别及应用场景

C++智能指针:shared_ptr、unique_ptr、weak_ptr区别及应用场景

编码文章call10242025-06-04 14:49:276A+A-

智能指针是C++中用于管理动态分配内存的重要工具,解决了传统裸指针容易导致的内存泄漏、悬空指针等问题。C++11引入了std::shared_ptrstd::unique_ptrstd::weak_ptr,它们分别适用于不同的场景。本文将详细介绍这三种智能指针的特点、应用场景,并通过代码示例展示其功能。


一、std::shared_ptr

1.1 介绍

std::shared_ptr是一种共享所有权的智能指针,允许多个指针共享同一个动态分配的对象。它通过引用计数(reference counting)机制管理内存,当最后一个shared_ptr销毁或重置时,对象才会被删除。

1.2 特点

  • 共享所有权:多个shared_ptr可以指向同一对象,引用计数跟踪共享指针数量。
  • 自动内存管理:当引用计数为0时,自动释放对象。
  • 线程安全计数:引用计数的增减操作是线程安全的,但对象本身的访问需用户自行保证。
  • 自定义删除器:支持用户定义的删除器,用于特殊资源管理。
  • 开销:由于维护引用计数,shared_ptr相比unique_ptr有额外的内存和性能开销。

1.3 应用场景

  • 需要多个指针共享同一资源,如对象在多个模块间传递。
  • 动态分配的资源生命周期不明确,需由引用计数决定。
  • 管理需要共享的复杂数据结构,如树或图。

1.4 代码示例

以下是一个使用std::shared_ptr管理共享资源的示例,展示其基本用法和引用计数管理。

 #include <iostream>
 #include <memory>
 #include <string>
 
 class Resource {
 public:
     Resource(const std::string& name) : name_(name) {
         std::cout << "Resource " << name_ << " created\n";
     }
     ~Resource() {
         std::cout << "Resource " << name_ << " destroyed\n";
     }
     void use() const {
         std::cout << "Using resource " << name_ << "\n";
     }
 private:
     std::string name_;
 };
 
 void sharedPtrDemo() {
     // 创建shared_ptr
     std::shared_ptr<Resource> res1 = std::make_shared<Resource>("R1");
     std::cout << "Reference count: " << res1.use_count() << "\n";
 
     // 共享所有权
     {
         std::shared_ptr<Resource> res2 = res1;
         std::cout << "Reference count after res2: " << res1.use_count() << "\n";
         res2->use();
     } // res2离开作用域,引用计数减1
 
     std::cout << "Reference count after res2 destroyed: " << res1.use_count() << "\n";
     res1->use();
 } // res1离开作用域,资源销毁

运行结果:

 Resource R1 created
 Reference count: 1
 Reference count after res2: 2
 Using resource R1
 Reference count after res2 destroyed: 1
 Using resource R1
 Resource R1 destroyed

在这个示例中,std::make_shared创建了一个shared_ptr,通过use_count()可以查看当前引用计数。res2共享了res1的资源,引用计数增至2,当res2销毁后,引用计数减为1,最终res1销毁时资源被释放。

二、std::unique_ptr

2.1 介绍

std::unique_ptr是独占所有权的智能指针,同一时刻只有一个unique_ptr可以指向某个对象。它轻量高效,适合需要明确所有权的场景。

2.2 特点

  • 独占所有权:不能复制,只能通过std::move转移所有权。
  • 轻量高效:没有引用计数,内存和性能开销低。
  • 自动释放:离开作用域时自动删除对象。
  • 自定义删除器:支持自定义删除器,但需在类型中指定。
  • 空指针安全:支持空指针状态,析构时无操作。

2.3 应用场景

  • 需要独占资源,避免共享带来的复杂性。
  • 作为函数返回值,传递动态分配的对象所有权。
  • 管理临时资源,确保资源在特定作用域内释放。

2.4 代码示例

以下示例展示了std::unique_ptr的独占所有权和所有权转移。

 #include <iostream>
 #include <memory>
 #include <string>
 
 class Resource {
 public:
     Resource(const std::string& name) : name_(name) {
         std::cout << "Resource " << name_ << " created\n";
     }
     ~Resource() {
         std::cout << "Resource " << name_ << " destroyed\n";
     }
     void use() const {
         std::cout << "Using resource " << name_ << "\n";
     }
 private:
     std::string name_;
 };
 
 std::unique_ptr<Resource> createResource(const std::string& name) {
     return std::make_unique<Resource>(name); // 返回unique_ptr
 }
 
 void uniquePtrDemo() {
     // 创建unique_ptr
     std::unique_ptr<Resource> res1 = createResource("U1");
     res1->use();
 
     // 转移所有权
     std::unique_ptr<Resource> res2 = std::move(res1);
     if (!res1) {
         std::cout << "res1 is null after move\n";
     }
     res2->use();
 
     // 自定义删除器示例
     auto deleter = [](Resource* ptr) {
         std::cout << "Custom deleter called\n";
         delete ptr;
     };
     std::unique_ptr<Resource, decltype(deleter)> res3(new Resource("U2"), deleter);
     res3->use();
 } // 离开作用域,资源自动销毁

运行结果:

 Resource U1 created
 Using resource U1
 res1 is null after move
 Using resource U1
 Resource U2 created
 Using resource U2
 Custom deleter called
 Resource U2 destroyed
 Resource U1 destroyed

此示例中,std::make_unique创建unique_ptr,通过std::move转移所有权后,res1变为空指针。还展示了自定义删除器的用法,适合特殊资源管理。

三、std::weak_ptr

3.1 介绍

std::weak_ptr是一种非拥有型智能指针,通常与std::shared_ptr配合使用。它不控制对象的生命周期,仅提供对shared_ptr管理对象的访问。

3.2 特点

  • 不拥有资源:不会增加shared_ptr的引用计数。
  • 检测悬空指针:通过expired()检查资源是否已被销毁。
  • 转换为shared_ptr:通过lock()获取shared_ptr以访问对象。
  • 避免循环引用:解决shared_ptr可能导致的循环引用问题。

3.3 应用场景

  • 打破shared_ptr的循环引用,如在树或图结构中。
  • 缓存或观察资源,需检查资源是否有效。
  • 临时访问共享资源,不影响其生命周期。

3.4 代码示例

以下示例展示std::weak_ptr如何避免循环引用和检查资源有效性。

 #include <iostream>
 #include <memory>
 #include <string>
 
 class Node {
 public:
     std::string name_;
     std::shared_ptr<Node> partner_;
     std::weak_ptr<Node> weak_partner_; // 使用weak_ptr避免循环引用
 
     Node(const std::string& name) : name_(name) {
         std::cout << "Node " << name_ << " created\n";
     }
     ~Node() {
         std::cout << "Node " << name_ << " destroyed\n";
     }
 };
 
 void weakPtrDemo() {
     // 创建两个共享节点
     std::shared_ptr<Node> node1 = std::make_shared<Node>("N1");
     std::shared_ptr<Node> node2 = std::make_shared<Node>("N2");
 
     // 设置循环引用
     node1->partner_ = node2;
     node2->partner_ = node1; // 循环引用,可能导致内存泄漏
 
     // 使用weak_ptr避免循环引用
     node1->weak_partner_ = node2;
     node2->weak_partner_ = node1;
 
     // 检查weak_ptr
     if (auto temp = node1->weak_partner_.lock()) {
         std::cout << "node1's weak_partner: " << temp->name_ << "\n";
     } else {
         std::cout << "node1's weak_partner is expired\n";
     }
 
     // 释放node2
     node2.reset();
     if (node1->weak_partner_.expired()) {
         std::cout << "node1's weak_partner is expired after node2 reset\n";
     }
 }

运行结果:

 Node N1 created
 Node N2 created
 node1's weak_partner: N2
 Node N2 destroyed
 node1's weak_partner is expired after node2 reset
 Node N1 destroyed

此示例中,partner_形成了循环引用,导致内存泄漏(若无其他干预)。而weak_partner_使用weak_ptr,不会增加引用计数,避免了循环引用问题。通过lock()expired()检查资源状态,确保安全访问。

四、比较与选择

特性

shared_ptr

unique_ptr

weak_ptr

所有权

共享

独占

引用计数

复制

支持

不支持(仅移动)

支持

性能开销

较高(引用计数)

较低

中等

典型场景

共享资源、复杂数据结构

独占资源、函数返回值

循环引用、资源观察

选择建议:

  • 如果资源需要独占,选择unique_ptr,性能更高且逻辑清晰。
  • 如果资源需要共享且生命周期复杂,选择shared_ptr
  • 如果需要访问shared_ptr管理的资源但不控制生命周期,或需打破循环引用,选择weak_ptr

五、注意事项

  1. 避免裸指针:不要将同一裸指针传递给多个智能指针,可能导致多次删除。
  2. 优先使用make函数std::make_sharedstd::make_unique更安全,减少内存分配次数。
  3. 循环引用shared_ptr可能导致循环引用,需用weak_ptr解决。
  4. 线程安全shared_ptr的引用计数线程安全,但对象访问需加锁保护。

六、总结

std::shared_ptrstd::unique_ptrstd::weak_ptr各有其独特的设计理念和应用场景。shared_ptr适合共享资源,unique_ptr强调独占所有权,weak_ptr则是解决循环引用和临时访问的利器。通过合理选择智能指针,开发者可以编写更安全、高效的C++代码。

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4