[C++面试] 智能指针面试点(重点)续3
[C++面试] RAII资源获取即初始化(重点)-CSDN博客
[C++面试] 智能指针面试点(重点)-CSDN博客
[C++面试] 智能指针面试点(重点)续1-CSDN博客
[C++面试] 智能指针面试点(重点)续2-CSDN博客
一、入门
1、unique_ptr
空智能指针创建与资源绑定:
std::unique_ptr<int> u_i; // 创建空智能指针(未持有任何资源)
u_i.reset(new int(3)); // 通过 reset() 绑定新资源(值为3的int对象)
reset()
是 unique_ptr
的成员函数,用于释放当前资源并绑定新资源。 空指针初始状态无资源所有权,reset()
会触发原资源释放(若存在)。
构造函数直接初始化:
std::unique_ptr<int> u_i2(new int(4)); // 创建时直接绑定动态分配的int对象(值为4)
// c++ 14之后
auto u_i3 = std::make_unique<int>(5); // 创建指向int的unique_ptr,值为5
auto u_s3 = std::make_unique<std::string>("def"); // 创建指向string的unique_ptr,值为"def
std::make_unique<T>(args)
是 C++14 引入的工厂函数
所有权释放与转移:
int *p_i = u_i2.release(); // 释放所有权,返回原生指针(u_i2变为空指针)
std::unique_ptr<std::string> u_s(new std::string("abc"));
std::unique_ptr<std::string> u_s2 = std::move(u_s); // 通过移动语义转移所有权
显式资源释放与重置:
u_s2.reset(u_s.release()); // 释放u_s资源并由u_s2接管
u_s2 = nullptr; // 显式销毁资源并置空(等价于 u_s2.reset())
手动释放原生指针:
delete p_i; // 释放通过 release() 获得的原生指针
“ 调用 release()
后,资源释放的责任转移到返回的原生指针,需手动管理”。
2、简单介绍一下reset和release
reset()
在 std::unique_ptr
中,其核心作用是 释放当前管理的资源(若存在),并可以选择绑定新的资源。
step1:若 unique_ptr
当前持有资源(即非空指针),调用 reset()
会立即通过 delete
或自定义删除器释放该资源
step2:若 reset()
接受参数(如 reset(new int(20))
),则释放旧资源后,unique_ptr
会接管新资源的所有权
std::unique_ptr<int> u_i2(new int(4));
u_i2.reset(new int(20)); // 释放原资源(int(4)),绑定新资源(int(20))
std::unique_ptr<int> u_i2(new int(4)); // 初始绑定资源(值为4的int对象)
u_i2.reset(); // 释放资源并置空,不再绑定任何资源
执行后状态:
u_i2.get() == nullptr
,表示无资源绑定。- 原资源
int(4)
已被销毁
shared_ptr
内部维护一个引用计数器(强引用计数),每次拷贝构造或赋值操作时,引用计数自增;每次析构或调用 reset()
时,引用计数自减。当引用计数归零时,自动释放资源。
auto sp1 = std::make_shared<int>(10); // 引用计数初始化为1
auto sp2 = sp1; // 引用计数增至2
sp2.reset(); // sp2释放资源,引用计数减至1
释放当前资源并绑定新资源(可选)。无参数时,仅释放资源并置空指针。
std::shared_ptr<int> p1(new int(5)); // 直接构造
auto p2 = std::make_shared<int>(10); // 推荐方式:避免内存碎片
p1.reset(new int(20)); // 释放原int(5),绑定新int(20)
p1.reset(); // 释放资源,p1变为空指针
注: 尽量通过移动语义传递 shared_ptr
,减少引用计数操作
release()
的作用是 释放 unique_ptr
对当前资源的所有权,但 不释放资源
释放所有权:release()
返回当前管理的原生指针(如 int*
),并将 unique_ptr
内部指针置为空(变为空指针)
调用 release()
后,资源释放的责任转移到返回的原生指针,需手动管理
std::unique_ptr<int> u_i2(new int(4));
int *p_i = u_i2.release(); // u_i2 变为空指针,p_i 持有原资源
delete p_i; // 必须手动释放,否则内存泄漏
原生指针是普通的 C++ 指针(如 int*
),它仅存储内存地址,不负责资源的生命周期管理。
3、unique_ptr相比于auto_ptr有什么优势?
unique_ptr
通过 编译期禁用拷贝构造函数和赋值运算符 彻底解决了 auto_ptr
的所有权隐式转移问题。同样都可能有Bug,但是unique_ptr可以在编译期解决,而auto_ptr要在运行期。
auto_ptr<int> ap1(new int(10));
auto_ptr<int> ap2 = ap1; // 允许赋值,但 ap1 变为空指针(运行时风险)
unique_ptr<int> up1(new int(10));
unique_ptr<int> up2 = up1; // 编译报错,禁止拷贝[1,6](@ref)
unique_ptr
通过 std::move
实现 显式的所有权转移,代码意图更清晰
unique_ptr<int> up1(new int(10));
unique_ptr<int> up2 = std::move(up1); // up1 被置空,所有权转移至 up2
vector<unique_ptr<int>> vec;
vec.push_back(std::move(up1)); // 合法
特性 | auto_ptr (C++98) | unique_ptr (C++11) |
---|---|---|
所有权转移 | 隐式(运行时风险) | 显式(std::move ,编译安全) |
拷贝/赋值 | 允许(导致悬空指针) | 禁用(编译报错) |
数组支持 | 不支持(delete 错误) | 支持(delete[] ) |
容器兼容性 | 不兼容 | 兼容(通过移动语义) |
自定义删除器 | 不支持 | 支持 |
线程安全性 | 无保障 | 需外部同步(引用计数非线程安全) |
4、 使用 unique_ptr
是否需要有效性判断?
- 所有权转移后:当通过
std::move
或release()
转移所有权后,原unique_ptr
会被置空,此时需检查有效性。 - 从外部接收指针:若不确定指针是否已被释放,应进行验证。
// 法1:
unique_ptr<int> up(new int(10));
if (up) { // 隐式调用 operator bool()
cout << *up << endl; // 有效时操作
}
// 法2:
if (up != nullptr) { /* ... */ }
// 法3:get() 方法:返回原生指针,若为空则无效
if (up.get() != nullptr) { /* ... */ }
避免手动操作原生指针:除非必要,不要通过 get()
或 release()
暴露内部指针
原生指针:是普通的 C++ 指针(如
int*
),它仅存储内存地址,不负责资源的生命周期管理。在release()
返回后,必须通过delete
或传递给其他智能指针手动释放资源
unique_ptr的一种惯用法
unique_ptr <string> demo(const char * s)
{
unique_ptr <string> temp (new string (s));
return temp;
}
unique_ptr <string> ps;
ps = demo("Uniquely special");
二、进阶
1、weak_ptr为啥取名叫weak?
被称为 "weak"(弱)指针,是因为它不拥有对象的 所有权,仅作为观察者存在,不会影响对象的生命周期。相比之下,std::shared_ptr
是强引用指针,直接参与对象所有权的管理。
weak_ptr
不拥有对象的所有权,不会增加对象的 强引用计数(即 shared_ptr
的引用计数)。
是观察者:类似于房屋中介,不拥有房子,但是可以带人看房。它通过观察 shared_ptr
管理的对象,提供一种安全访问的机制。即使对象已被释放,weak_ptr
也能检测到这一状态,避免悬空指针问题
auto sp = std::make_shared<int>(10); // 强引用计数=1
std::weak_ptr<int> wp(sp); // 弱引用计数=1,强引用计数仍为1
shared_ptr
:可直接通过operator*
或operator->
访问对象。weak_ptr
:必须通过lock()
方法转换为shared_ptr
后才能访问对象。会尝试将weak_ptr
转换为std::shared_ptr
,从而 临时获取资源的强引用。如果原资源已被释放,lock()
会返回空指针,避免访问无效内存
if (auto tmp = wp.lock()) { // 原子操作,安全检测对象是否存活
std::cout << *tmp << std::endl;
}
lock()
是原子操作,保证在多线程中安全检测对象状态。但避免在 lock()
调用期间资源被其他线程释放。
2、weak_ptr不调用lock可以吗?
不调用 lock()
的后果
- 直接访问是未定义行为
weak_ptr
没有重载operator*
或operator->
,无法直接解引用或访问对象成员。若强制转换或通过其他方式(如get()
)获取原生指针,可能导致悬空指针访问,引发程序崩溃或数据损坏 - 若通过
weak_ptr
的get()
方法获取原生指针,即使资源未被释放,也可能因强引用计数归零后随时被销毁,无法保证生命周期。 - 无法保证资源有效性
即使通过expired()
检查资源是否存活,在多线程环境中仍可能出现竞态条件(例如检查后资源被其他线程释放)
虽然 expired()
可以检测资源是否存活,但它仅提供瞬时状态,无法保证后续操作的安全性。因此仍需配合 lock()
使用
if (!wp.expired()) { // 不可靠的瞬时检查
auto sp = wp.lock(); // 仍需再次确认
sp->doSomething();
}