智能指针面试题
智能指针面试题
1.介绍一下shared_ptr的底层实现
std::shared_ptr
的底层结构分为两部分:
控制块(Control Block):一个动态分配的小型数据结构,用于存储引用计数和其他元信息。
- 强引用计数(Strong Reference Count):记录有多少个
shared_ptr
引用了该对象。 - 弱引用计数(Weak Reference Count):记录有多少个
std::weak_ptr
引用了该对象(用于支持std::weak_ptr
)。 - 删除器(Deleter):可选的自定义函数,用于释放对象时执行特定操作。
- 分配器(Allocator):可选的自定义分配器,用于管理内存分配和释放。
- 强引用计数(Strong Reference Count):记录有多少个
原始指针(Raw Pointer):保存一个指向实际对象的原始指针
2.介绍一下shared_ptr的引用计数的操作
增加引用计数:当一个 shared_ptr
被拷贝(通过拷贝构造函数或赋值操作符)时,控制块中的强引用计数加 1。
减少引用计数:当一个 shared_ptr
被销毁或重置时,控制块中的强引用计数减 1。
- 强引用计数变为 0:
- 调用删除器(如果有)来释放原始指针指向的对象。
- 如果弱引用计数也为 0,则释放控制块本身。
3.weak_ptr能直接访问对象吗
std::weak_ptr
本身不能直接访问对象,std::weak_ptr
可以通过检查控制块的状态来判断对象是否仍然有效。
间接访问:
检查对象是否仍然有效: 调用
std::weak_ptr::lock()
方法,尝试将std::weak_ptr
提升为一个std::shared_ptr
- 强引用计数为 0(即对象已经被销毁),
lock()
返回一个空的std::shared_ptr
- 强引用计数大于 0(即对象仍然有效),
lock()
返回一个新的std::shared_ptr
,并增加强引用计数。
- 强引用计数为 0(即对象已经被销毁),
提升为
std::shared_ptr
: 如果std::weak_ptr::lock()
成功返回一个有效的std::shared_ptr
,则可以通过这个std::shared_ptr
访问对象。
4.shared_ptr和unique_ptr的区别
所有权:
std::unique_ptr
:实现了独占所有权语义。std::shared_ptr
:实现了共享所有权语义。
性能与内存开销:
std::unique_ptr
:性能较高,因为它不需要额外的内存开销来存储引用计数或其他元数据。std::shared_ptr
:性能较低,因为每次增加或减少引用计数都需要原子操作。还需要额外的控制块来存储引用计数和其他信息。
移动语义 vs 复制语义:
std::unique_ptr
:支持移动语义,但不支持复制语义。std::shared_ptr
:支持复制和赋值操作。
5.shared_ptr的循环引用问题是什么?如何解决?
循环引用: std::shared_ptr
的循环引用问题是指两个或多个对象通过 std::shared_ptr
相互持有对方,导致它们的强引用计数永远不会降为 0,从而无法释放内存。
解决方案:用 std::weak_ptr
替代部分 std::shared_ptr
1 |
|
6.shared_ptr是否线程安全?
7.shared_ptr的构造方法有哪几种,为什么尽量使用make_shared?
构造方法:
默认构造函数:
1
std::shared_ptr<int> ptr; // 空 shared_ptr
从原始指针构造
1
2int* rawPtr = new int(42);
std::shared_ptr<int> ptr(rawPtr); // 用裸指针初始化 shared_ptr注:这种方式需要手动分配内存(如
new
),容易导致资源泄漏或双重释放。使用
std::make_shared
1
auto ptr = std::make_shared<int>(42);
使用删除器:
1
std::shared_ptr<int> ptr(new int(42), [](int* p) { delete p; });
为什么尽量使用make_shared:
减少内存分配次数:裸指针需要两次内存分配,而
std::make_shared
将对象和控制块合并到一次内存分配中,减少了内存分配的开销。1
2
3
4
5
6// 使用裸指针构造 shared_ptr
int* rawPtr = new int(42); // 分配对象
std::shared_ptr<int> ptr1(rawPtr); // 分配控制块
// 使用 make_shared
auto ptr2 = std::make_shared<int>(42); // 合并对象和控制块的一次分配异常安全性: 在分配对象后、构造
std::shared_ptr
前抛出异常,则会导致内存泄漏。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 使用裸指针可能引发异常安全性问题
try {
int* rawPtr = new int(42); // 分配对象
throw std::runtime_error("Error"); // 抛出异常,rawPtr 未被管理
std::shared_ptr<int> ptr(rawPtr); // 这行代码不会被执行
} catch (...) {
// 内存泄漏
}
// 使用 make_shared 是安全的
try {
auto ptr = std::make_shared<int>(42); // 单步完成分配和管理
throw std::runtime_error("Error"); // 异常安全,ptr 会正确释放资源
} catch (...) {
// 资源被正确释放
}