深入理解 C++ 智能指针:unique_ptr、shared_ptr 和 weak_ptr 的原理与实战

文摘   2024-12-22 09:00   浙江  

要说 C++ 的内存管理,手动管理指针那可是“事故高发地带”。一不小心分配的内存忘了释放,程序的内存就像漏水的水管,跑得干干净净。后来,C++11 引入了 智能指针 ,就像请了一个专业管家,帮你管好这些琐事。今天咱们聊聊智能指针的三位大明星:unique_ptrshared_ptr 和 weak_ptr,看看它们是怎么工作的,又能怎么用。


unique_ptr:独占式的智能指针

unique_ptr 是个“一人吃饱,全家不饿”的主儿。它对所管理的资源有唯一的所有权,哪怕你想偷偷分它一份,那也是不可能的。


代码示例

#include <iostream>

#include <memory>

void demo_unique_ptr() {

std::unique_ptr<int> uptr = std::make_unique<int>(42); // 创建一个智能指针,管理一个整数

std::cout << “Value: ” << *uptr << std::endl;

// std::unique_ptr<int> uptr2 = uptr; // 编译错误:unique_ptr 不能被复制

std::unique_ptr<int> uptr2 = std::move(uptr); // 转移所有权

if (!uptr) {

std::cout << “uptr is now nullptr” << std::endl;

}

}

输出结果 :


Value: 42

uptr is now nullptr

用法解析

unique_ptr 的设计哲学很简单:它就是资源的唯一主人。如果你硬要把资源转让,就只能用 std::move。这就像你把钥匙交给别人,你手里就没钥匙了。


应用场景

适合那些需要明确资源唯一性的时候,比如管理某些临时对象,或者资源初始化后就不需要频繁转交的场景。


温馨提示 :别试图复制 unique_ptr,它会直接让编译器跟你翻脸。


shared_ptr:共享资源的智能指针

shared_ptr 是个“大家庭”的代表。它允许多个指针共享同一份资源,等最后一个指针离开,这份资源才会被释放。


代码示例

#include <iostream>

#include <memory>

void demo_shared_ptr() {

std::shared_ptr<int> sptr1 = std::make_shared<int>(100); // 创建共享指针

std::shared_ptr<int> sptr2 = sptr1; // 共享同一资源

std::cout << “Value: ” << *sptr1 << “, Use count: ” << sptr1.use_count() << std::endl;

sptr2.reset(); // sptr2 放弃对资源的管理

std::cout << “After reset, Use count: ” << sptr1.use_count() << std::endl;

}

输出结果 :


Value: 100, Use count: 2

After reset, Use count: 1

用法解析

shared_ptr 内部维护一个引用计数,每当有一个新的 shared_ptr 指向它的资源时,计数器就加 1。当某个 shared_ptr 离开作用域或者调用 reset(),计数器减 1。计数器归零时,资源才会自动释放。


应用场景

适合那些需要多个对象共享同一资源的场景,比如多个子系统需要访问同一个配置对象。


温馨提示 :当两个 shared_ptr 互相引用时,会造成循环引用,资源永远无法释放。这时候你需要 weak_ptr 出马。


weak_ptr:打破循环引用的工具

weak_ptr 是个“旁观者”。它不会参与引用计数,但可以观察资源是否还存在。当 shared_ptr 的资源释放时,weak_ptr 就会自动失效。


代码示例

#include <iostream>

#include <memory>

void demo_weak_ptr() {

std::shared_ptr<int> sptr = std::make_shared<int>(200);

std::weak_ptr<int> wptr = sptr; // 创建一个 weak_ptr 观察 shared_ptr 的资源

if (auto locked = wptr.lock()) { // 尝试获取 shared_ptr

std::cout << “Value: ” << *locked << std::endl;

}

sptr.reset(); // 释放资源

if (wptr.expired()) {

std::cout << “Resource has been released.” << std::endl;

}

}

输出结果 :


Value: 200

Resource has been released.

用法解析

weak_ptr 的核心是打破循环引用。它不会增加引用计数,但可以通过 lock() 方法临时拿到一个 shared_ptr,用来访问资源。资源释放后,weak_ptr.lock() 会返回一个空的指针。


应用场景

适合观察但不拥有资源的场景,尤其是 shared_ptr 之间可能存在循环引用的情况,比如双向关联的对象。


温馨提示 :weak_ptr 是用来观察的,不要想着用它直接操作资源,否则你会得到一堆空指针错误。


4

三者的对比与选择

类型
特点
应用场景
unique_ptr
独占所有权,不能复制
独占资源的管理,比如文件句柄
shared_ptr
引用计数,支持多指针共享资源
多方需要共享资源,比如缓存或配置
weak_ptr
不参与引用计数,避免循环引用
观察资源生命周期,比如双向关联对象

5

实战案例:智能指针管理树结构

假设我们有一个简单的树结构,父节点和子节点需要互相引用,这时候 weak_ptr 就派上用场了。


代码示例

#include <iostream>

#include <memory>

#include <vector>

struct Node {

int value;

std::vector<std::shared_ptr<Node>> children;

std::weak_ptr<Node> parent; // 使用 weak_ptr 避免循环引用

Node(int val) : value(val) {}

};

void demo_tree() {

auto root = std::make_shared<Node>(1);

auto child1 = std::make_shared<Node>(2);

auto child2 = std::make_shared<Node>(3);

root->children.push_back(child1);

root->children.push_back(child2);

child1->parent = root;

child2->parent = root;

std::cout << “Root: ” << root->value << “, Child1: ” << child1->value << std::endl;

}

输出结果 :


Root: 1, Child1: 2

这里如果 parent 用的是 shared_ptr,就会造成循环引用,root 和 child 永远不会释放。而 weak_ptr 则完美解决了这个问题。


6

本次学习的要点

  • unique_ptr
     :独占资源,适合需要唯一所有权的场景。
  • shared_ptr
     :共享资源,适合需要多方共享的场景,但要小心循环引用。
  • weak_ptr
     :旁观者,主要用来打破循环引用。


写 C++ 的时候,智能指针就像你的好帮手,既能让代码更简洁,也能让内存管理更安全。工具是死的,怎么用还得看你。



椰子树的秘密
优质内容创作者
 最新文章