后宫大酒店0.8汉化安卓版
15.23MB · 2025-12-25
好的,非常乐意为你详细讲解 C++ 智能指针。这是现代 C++(C++11 及之后)中最重要的特性之一,它彻底改变了 C++ 的内存管理方式。
我们将从为什么需要它开始,然后逐一介绍三种核心的智能指针:std::unique_ptr、std::shared_ptr 和 std::weak_ptr。
在 C++ 中,我们经常使用 new 在**堆 (Heap)**上动态分配内存。
C++
void old_way() {
MyClass* raw_ptr = new MyClass(); // 1. 分配内存
raw_ptr->do_something();
// ...
delete raw_ptr; // 2. 必须手动释放
}
这种“裸指针” (Raw Pointer) 的管理方式非常危险,极易出错:
内存泄漏 (Memory Leaks) :
delete:最常见的问题。raw_ptr 是一个局部变量,当 old_way() 函数结束时,raw_ptr 变量本身被销毁了,但它所指向的在“堆”上的 MyClass 对象依然存在,这块内存永远无法被回收。new 和 delete 之间有 return、break 或 continue,delete 语句可能被跳过。do_something() 中抛出了异常,delete raw_ptr; 这一行永远不会被执行,内存泄漏。悬挂指针 (Dangling Pointers) :
悬空指针 :
delete 后,它并没有自动变为 nullptr,它依然指向那块“已释放”的内存。如果后续代码不慎再次使用了这个指针(“Use-After-Free”),会导致未定义行为(通常是程序崩溃)。所有权 (Ownership) 不明:
delete 它?delete 同一个指针,会引发二次释放 (Double Free) ,导致程序崩溃。delete,就是内存泄漏。为了解决这些问题,C++ 引入了智能指针 (Smart Pointers) 。
智能指针不是指针,它是一个对象(一个类模板)。它封装(wrap)了一个裸指针,并利用了 C++ 的一个核心特性:RAII (Resource Acquisition Is Initialization) ,即“资源获取即初始化”。
RAII 的核心思想:
new 出来的内存)。delete 封装的裸指针)。智能指针就是这样一个 RAII 对象。你把 new 得到的指针交给它,你就可以“忘记”delete 了。当智能指针对象生命周期结束时,它会在析构函数中自动帮你 delete 掉它所管理的指针。
C++
#include <memory> // 智能指针的头文件
void smart_way() {
// std::make_unique 是创建 unique_ptr 的推荐方式 (C++14)
std::unique_ptr<MyClass> smart_ptr = std::make_unique<MyClass>();
smart_ptr->do_something();
// 当 smart_way() 函数返回时,smart_ptr 离开作用域
// 它的析构函数被自动调用
// 析构函数会自动执行 delete smart_ptr.get()
} // <-- 内存在这里被自动释放,即使发生异常也一样!
std::unique_ptr (独占指针)std::unique_ptr 是最常用、最高效、最“轻量级”的智能指针。
核心概念: 独占所有权 (Exclusive Ownership) 。
含义: 在任何时刻,只有一个 unique_ptr 可以指向某个特定的对象。它“拥有”这个对象。
特性:
unique_ptr,因为这会违反“独占”所有权。unique_ptr 转移 (move) 给另一个。C++
#include <memory>
#include <utility> // for std::move
// 推荐的创建方式 (C++14)
std::unique_ptr<MyClass> p1 = std::make_unique<MyClass>();
// 像裸指针一样使用
p1->do_something();
std::cout << (*p1).value;
// 1. 编译错误:不允许复制!
// std::unique_ptr<MyClass> p2 = p1; // ERROR: copy constructor is deleted
// 2. 正确:转移所有权 (Move)
// p1 将放弃所有权,并变为空 (nullptr)
std::unique_ptr<MyClass> p2 = std::move(p1);
if (p1 == nullptr) {
std::cout << "p1 现在是空的" << std::endl;
}
if (p2 != nullptr) {
std::cout << "p2 拥有了该对象" << std::endl;
p2->do_something();
} // <-- p2 在这里离开作用域,释放 MyClass 对象
// 3. 从函数返回 (所有权转移)
std::unique_ptr<MyClass> create_object() {
// 工厂函数返回一个对象的所有权
return std::make_unique<MyClass>();
}
std::unique_ptr<MyClass> p3 = create_object();
// 4. 作为参数传递 (转移所有权)
void take_ownership(std::unique_ptr<MyClass> p) {
p->do_something();
} // <-- p 在这里析构,释放对象
take_ownership(std::move(p3));
// p3 在此之后也变为空
unique_ptr?答案:默认首选! 只要你不需要多个指针“共享”一个对象,就应该用 unique_ptr。
std::vector<std::unique_ptr<MyClass>>)。std::shared_ptr (共享指针)std::shared_ptr 解决了“多个指针需要指向同一对象”的场景。
核心概念: 共享所有权 (Shared Ownership) 。
含义: 多个 shared_ptr 可以指向同一个对象。它们通过引用计数 (Reference Counting) 来管理这个对象的生命周期。
特性:
shared_ptr 被复制(或赋值给另一个 shared_ptr)时,计数器 +1。shared_ptr 被析构(或被赋新值)时,计数器 -1。shared_ptr 消失了,它会在析构时 delete 掉被管理的对象。unique_ptr 重。它需要额外分配“控制块”的内存,并且增减计数器必须是原子操作(线程安全),这有性能开销。C++
#include <memory>
// 推荐的创建方式 (C++11)
std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>();
std::cout << "当前引用计数: " << sp1.use_count() << std::endl; // 输出: 1
{
// 复制构造函数,sp2 也指向 sp1 的对象
std::shared_ptr<MyClass> sp2 = sp1;
std::cout << "当前引用计数: " << sp1.use_count() << std::endl; // 输出: 2
// sp2 和 sp1 都可以使用
sp2->do_something();
} // <-- sp2 在这里离开作用域,析构
// 引用计数 -1,变为 1
std::cout << "当前引用计数: " << sp1.use_count() << std::endl; // 输出: 1
// 为什么用 make_shared?
// 1. 性能:MyClass 对象和“控制块”的内存可以一次性分配,减少内存分配次数。
// 2. 异常安全:避免了在 `new MyClass()` 成功但 `shared_ptr` 构造失败时导致的内存泄漏。
shared_ptr?当你无法明确界定“谁是唯一的所有者”时。
this 指针可能在回调被调用前失效,使用 shared_from_this)。std::weak_ptr (弱指针)shared_ptr 有一个致命问题:循环引用 (Circular Reference) 。
C++
struct NodeB;
struct NodeA {
std::shared_ptr<NodeB> b_ptr;
~NodeA() { std::cout << "NodeA 析构n"; }
};
struct NodeB {
std::shared_ptr<NodeA> a_ptr;
~NodeB() { std::cout << "NodeB 析构n"; }
};
void circular_reference_problem() {
auto a = std::make_shared<NodeA>(); // a 的计数 = 1
auto b = std::make_shared<NodeB>(); // b 的计数 = 1
a->b_ptr = b; // b 的计数 = 2 (a 持有 b)
b->a_ptr = a; // a 的计数 = 2 (b 持有 a)
} // <-- 函数结束,a 和 b 两个 shared_ptr 离开作用域
// a 的计数从 2 -> 1
// b 的计数从 2 -> 1
// **内存泄漏!**
// a 的计数是 1 (因为 b 还指着它),所以 NodeA 不会被析构。
// b 的计数是 1 (因为 a 还指着它),所以 NodeB 也不会被析构。
// 它们互相“续命”,导致谁也无法释放。
std::weak_ptrweak_ptr 是一种非拥有 (Non-owning) 的智能指针。
核心概念: 它只是一个“观察者”。
含义: 它可以指向 shared_ptr 管理的对象,但它不会增加引用计数。
特性:
weak_ptr 使用对象(没有 -> 或 * 操作符)。C++
struct NodeB_Fixed;
struct NodeA_Fixed {
std::shared_ptr<NodeB_Fixed> b_ptr; // A 强引用 B
~NodeA_Fixed() { std::cout << "NodeA 析构n"; }
};
struct NodeB_Fixed {
// B 弱引用 A
std::weak_ptr<NodeA_Fixed> a_ptr; // <-- 关键!
~NodeB_Fixed() { std::cout << "NodeB 析构n"; }
};
void circular_reference_solution() {
auto a = std::make_shared<NodeA_Fixed>(); // a 计数 = 1
auto b = std::make_shared<NodeB_Fixed>(); // b 计数 = 1
a->b_ptr = b; // b 计数 = 2
b->a_ptr = a; // a 计数 = 1 (weak_ptr 不增加计数)
}
// 函数结束:
// 1. a (shared_ptr) 离开作用域,a 计数从 1 -> 0。
// 2. NodeA 被析构!
// 3. 在 NodeA 的析构函数中,a->b_ptr (shared_ptr) 被销毁。
// 4. b 计数从 2 -> 1。
// 5. b (shared_ptr) 离开作用域,b 计数从 1 -> 0。
// 6. NodeB 被析构!
// (顺序可能是 5->6->3->4->1->2,但结果一样)
// **内存被正确释放!**
weak_ptr?你需要先把它“锁住” (.lock()),尝试获取一个临时的 shared_ptr:
C++
// 假设你有一个 weak_ptr
std::weak_ptr<MyClass> wp = ...;
// 尝试提升 (lock) 为 shared_ptr
if (std::shared_ptr<MyClass> sp = wp.lock()) {
// 提升成功!
// 这意味着在 if 语句块内,对象是存活的
// sp 保证了对象在此期间不会被释放
sp->do_something();
} else {
// 提升失败,对象已经被销毁了
std::cout << "对象已不存在" << std::endl;
}
weak_ptr?shared_ptr 的循环引用(如上例中的父子/A-B 关系)。std::map<Key, std::weak_ptr<Value>> 就很合适。Subject 可以持有 std::vector<std::weak_ptr<Observer>>,当 Observer 自行销毁时,Subject 不需要知道,它在通知时只需检查 lock() 是否成功。首选 std::make_unique (C++14) :当你创建对象时,优先使用 unique_ptr。
需要共享时才用 std::make_shared:当你明确知道所有权需要被共享时,才使用 shared_ptr。
使用 weak_ptr 打破 shared_ptr 循环:当你发现 shared_ptr 构成了循环(例如树结构中的“子节点指向父节点”),使用 weak_ptr 作为“非拥有”的一方。
不要混用裸指针和智能指针:
MyClass* raw = new MyClass();std::shared_ptr<MyClass> p1(raw);std::shared_ptr<MyClass> p2(raw);p1 和 p2 都有各自的引用计数(都为1),它们都会在析构时尝试 delete raw,导致二次释放。如何向函数传递智能指针?(非常重要)
情况1:函数只是“使用”对象,不关心所有权。
MyClass*) 或引用 (MyClass&)。unique_ptr、shared_ptr 或普通栈对象。void use_object(MyClass* obj) { obj->do_something(); }
// 调用:
use_object(my_unique_ptr.get());
use_object(my_shared_ptr.get());
情况2:函数需要“共享”所有权(例如,把它存为成员)。
shared_ptr。C++
void store_object(const std::shared_ptr<MyClass>& ptr) {
m_member_ptr = ptr; // 复制,引用计数+1
}
情况3:函数需要“夺取”或“转移”所有权。
unique_ptr(需要 std::move)。C++
void take_ownership(std::unique_ptr<MyClass> ptr) {
m_member_ptr = std::move(ptr); // 接收所有权
}
// 调用:
take_ownership(std::move(my_unique_ptr));
15.23MB · 2025-12-25
248.99MB · 2025-12-25
1.25 GB · 2025-12-25