C++ 的智能指针是用来自动管理动态内存(避免内存泄漏和悬空指针)的一类模板类,主要定义在 <memory> 头文件中。
它们本质上是对原始指针(raw pointer)的封装类模板,通过引用计数或所有权机制来自动释放资源。
🧩 一、智能指针的主要种类
| 智能指针 | 所属头文件 | 所有权模型 | 主要特点 |
|---|---|---|---|
std::auto_ptr (C++98, 已废弃) | <memory> | 独占所有权(转移语义不安全) | 已在 C++11 废弃,C++17 删除,不推荐使用 |
std::unique_ptr | <memory> | 独占所有权(唯一持有) | 无引用计数,不能复制,只能移动 |
std::shared_ptr | <memory> | 共享所有权(引用计数) | 支持多个指针共享同一资源,计数为 0 时自动释放 |
std::weak_ptr | <memory> | 弱引用(不计入引用计数) | 与 shared_ptr 配合使用,解决循环引用问题 |
🧠 二、各类智能指针详细区别
1. std::unique_ptr —— 独占所有权指针
-
核心特征:
-
同一时间内只能有一个
unique_ptr拥有资源。 -
不能被复制(删除了拷贝构造和赋值函数)。
-
可以通过
std::move转移所有权。
-
-
适用场景:
-
对象在某个作用域中唯一存在。
-
避免动态分配内存后忘记
delete。 -
示例:
-
#include <memory>
#include <iostream>struct Test {
Test() { std::cout << "Construct\n"; }
~Test() { std::cout << "Destruct\n"; }
};int main() {
std::unique_ptr<Test> p1 = std::make_unique<Test>();
// std::unique_ptr<Test> p2 = p1; // ❌ 不可复制
std::unique_ptr<Test> p2 = std::move(p1); // ✅ 转移所有权
}
-
2. std::shared_ptr —— 共享所有权指针
-
核心特征:
-
多个
shared_ptr可共享同一资源。 -
内部维护一个引用计数器(use_count)。
-
当最后一个
shared_ptr被销毁时,资源自动释放。
-
-
适用场景:
-
多个对象共享同一个动态资源。
-
3. std::weak_ptr —— 弱引用指针
-
核心特征:
-
不增加引用计数。
-
用于解决
shared_ptr循环引用问题。 -
不能直接解引用,必须通过
lock()获取临时的shared_ptr。
-
-
适用场景:
-
观察者模式(Observer Pattern)。
-
避免循环引用。
-
-
循环引用问题示例:
-
#include <memory>
-
struct B; // 前向声明
struct A {
std::shared_ptr<B> b_ptr;
};
struct B {
std::shared_ptr<A> a_ptr; // ❌ 互相持有 shared_ptr,会造成循环引用
};// 改进版
struct B_fixed {
std::weak_ptr<A> a_ptr; // ✅ 弱引用,不增加计数
};4.
std::auto_ptr(已废弃) -
C++98 中引入,但因为:
-
拷贝操作会转移所有权;
-
无法与标准容器、安全语义兼容;
在 C++11 中被
unique_ptr替代。 -
-
⚖️ 三、区别总结表
| 特性 | unique_ptr | shared_ptr | weak_ptr | auto_ptr(废弃) |
|---|---|---|---|---|
| 所有权 | 独占 | 共享 | 弱引用 | 独占(复制转移) |
| 引用计数 | 否 | 是 | 否 | 否 |
| 拷贝 | ❌ | ✅ | ✅(仅指向) | ✅(转移所有权) |
| 移动 | ✅ | ✅ | ✅ | ❌ |
| 循环引用问题 | 无 | 有 | 无 | 有 |
| 推荐使用 | ✅ | ✅ | ✅(辅助) | ❌ |
四、使用建议
-
首选
std::unique_ptr-
若资源只有唯一所有者;
-
性能最佳,无引用计数开销。
-
-
使用
std::shared_ptr-
若资源需要被多个对象共享;
-
注意循环引用。
-
-
配合
std::weak_ptr-
用于解决
shared_ptr的循环依赖; -
用于“观察者”或缓存等场景。
-
🧩 一、基础题
1. 什么是智能指针?为什么要使用智能指针?
答:
智能指针是对原始指针(raw pointer)的封装,用于自动管理动态内存的生命周期。
当智能指针离开作用域时,会自动调用 delete 释放资源,避免:
-
内存泄漏;
-
悬空指针;
-
重复释放等问题。
2. C++ 中有哪些智能指针?区别是什么?
| 智能指针 | 所有权 | 引用计数 | 特点 |
|---|---|---|---|
unique_ptr | 独占 | 否 | 不可复制,只能转移(std::move) |
shared_ptr | 共享 | 是 | 多个指针共享资源,引用计数为0时释放 |
weak_ptr | 弱引用 | 否 | 不增加计数,防止循环引用 |
auto_ptr(废弃) | 独占 | 否 | 拷贝时转移所有权,不安全,已被废弃 |
3. unique_ptr 可以被复制吗
不能。
unique_ptr 的拷贝构造函数和赋值操作符被 删除。
如果想要转移所有权,可以使用 std::move()。
std::unique_ptr<int> p1 = std::make_unique<int>(5);
// std::unique_ptr<int> p2 = p1; ❌ 编译错误
std::unique_ptr<int> p2 = std::move(p1); ✅ // 所有权转移
4. shared_ptr 的引用计数是如何实现的?
答:
shared_ptr 通过一个**控制块(control block)**管理:
-
保存指向对象的指针;
-
保存引用计数和弱引用计数。
当:
-
新的
shared_ptr指向相同对象 → 计数 +1 -
一个
shared_ptr析构 → 计数 -1
当计数归零时,自动调用delete释放内存。 -
5. 为什么要有
weak_ptr?
答:
为了解决 shared_ptr 的**循环引用(circular reference)**问题。
如果两个对象互相持有 shared_ptr,计数永远不会为 0,导致内存泄漏。
weak_ptr 不会增加引用计数,因此可打破循环。
⚙️ 二、代码理解题
6. 下面的代码会不会内存泄漏?为什么?
struct B;
struct A {
std::shared_ptr<B> bptr;
~A() { std::cout << "A destroyed\n"; }
};
struct B {
std::shared_ptr<A> aptr;
~B() { std::cout << "B destroyed\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->bptr = b;
b->aptr = a;
}
答:
会内存泄漏。
a 和 b 互相持有 shared_ptr,形成循环引用,引用计数都不为 0,析构函数不会被调用。
✅ 解决方法:
struct B {
std::weak_ptr<A> aptr; // 改为弱引用
};
7. shared_ptr 和 weak_ptr 如何协同工作?
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp; // 不增加引用计数
if (auto p = wp.lock()) { // 尝试获取 shared_ptr
std::cout << *p << std::endl;
} else {
std::cout << "对象已被销毁\n";
}
8. 如何自定义 shared_ptr 的删除器(deleter)?
#include <memory>
#include <iostream>
struct FileCloser {
void operator()(FILE* fp) const {
if (fp) fclose(fp);
std::cout << "File closed\n";
}
};
int main() {
std::shared_ptr<FILE> file(fopen("data.txt", "r"), FileCloser());
}
自定义删除器可用于释放非 new 分配的资源(如文件、socket 等)。
🧮 三、进阶题
9. shared_ptr 是线程安全的吗?答:
-
对同一个
shared_ptr实例的引用计数操作是线程安全的; -
但对同一资源的读写操作不是线程安全的;
-
若多个线程同时修改底层对象,需要加锁或使用
std::atomic。
10. 智能指针与原始指针混用会出什么问题?
-
不能用
shared_ptr管理一个已经由shared_ptr管理的原始指针; -
否则会导致重复释放(double free)。
int* p = new int(10);
std::shared_ptr<int> sp1(p);
std::shared_ptr<int> sp2(p); // ❌ 两个不同控制块,析构两次!
✅ 正确写法:
auto sp1 = std::make_shared<int>(10);
auto sp2 = sp1; // 共享同一控制块
11. 为什么推荐使用 std::make_shared?
-
避免两次内存分配;
-
提高性能;
-
防止异常安全问题。
std::make_shared 会在一次内存分配中同时创建控制块和对象,减少开销。
12. 智能指针可以管理数组吗? 答:
-
shared_ptr可以管理数组,但需要提供自定义删除器; -
unique_ptr支持数组版本:std::unique_ptr<int[]> arr(new int[10]);
13. shared_ptr 的循环引用一定要用 weak_ptr 吗?答:
几乎是的。
如果你能确定某个方向的引用不应控制对象生命周期(比如父引用子为强引用,子引用父为弱引用),就应使用 weak_ptr。
14. 能否从 unique_ptr 转换成 shared_ptr?
通过 std::move 转移所有权:
std::unique_ptr<int> up = std::make_unique<int>(42);
std::shared_ptr<int> sp = std::move(up); // ok
但反过来(shared_ptr → unique_ptr)不行。
✅ 总结建议
| 使用场景 | 推荐指针类型 |
|---|---|
| 单一所有者 | std::unique_ptr |
| 多个共享所有者 | std::shared_ptr |
| 打破循环依赖 | std::weak_ptr |
| 兼容旧代码(不推荐) | std::auto_ptr(已废弃) |
774

被折叠的 条评论
为什么被折叠?



