[C++面试] 智能指针面试点(重点)续4

[C++面试] RAII资源获取即初始化(重点)-优快云博客

[C++面试] 智能指针面试点(重点)-优快云博客

[C++面试] 智能指针面试点(重点)续1-优快云博客

[C++面试] 智能指针面试点(重点)续2-优快云博客

[C++面试] 智能指针面试点(重点)续3-优快云博客


一、入门

1、使用智能指针或者RAII技术过程中,如果出现了异常,会发生什么?

栈展开保证析构:C++的异常处理机制会确保所有已构造的局部对象析构函数被调用,无论是否发生异常

RAII

  • RAII(资源获取即初始化)的核心是通过对象的构造函数获取资源,析构函数释放资源。
  • 当异常发生时,C++会执行栈展开(Stack Unwinding)​,即销毁当前作用域及调用链中所有局部对象,触发析构函数。这一机制确保了即使抛出异常,资源仍会被自动释放

智能指针同理:栈展开(Stack Unwinding)

void processData() {
    auto ptr = std::make_unique<int>(42); // RAII对象构造时获取内存
    throw std::runtime_error("模拟异常"); // 抛出异常
    // 后续代码不会执行,但ptr的析构函数仍会被调用
} // 栈展开时,unique_ptr析构,自动释放内存

shared_ptr有什么不同的吗? 

void sharedExample() {
    auto ptr1 = std::make_shared<int>(10);
    auto ptr2 = ptr1; // 引用计数+1
    throw std::logic_error("错误"); 
} // 栈展开时,引用计数减至0,释放内存

析构顺序:局部对象ptr2ptr1会按照构造的逆序依次析构 

  • ptr2析构:引用计数从2减少到1。
  • ptr1析构:引用计数从1减少到0,此时对象的内存被释放,控制块(包含引用计数)也被销毁

多线程场景下呢?

shared_ptr的引用计数(_M_use_count)是原子变量(_Atomic_word),析构时的递减操作是线程安全的.

即使异常发生在多线程环境下,引用计数的修改也不会导致竞争条件

2、shared_ptr拷贝时拷贝的是什么?

当拷贝一个shared_ptr时,​拷贝的是其内部的控制块指针​(而非原始对象本身),并递增该控制块中的强引用计数

控制块:包含三部分信息

  • 强引用计数​(strong_count):记录当前有多少个shared_ptr共享该对象。
  • 删除器和原始指针:用于释放资源和访问对象。
  • ​弱引用计数​(weak_count):记录weak_ptr的引用次数(不影响对象生命周期)。

共享所有权:所有shared_ptr实例共享同一个控制块,通过引用计数跟踪资源的使用情况。只有当强引用计数归零时,才会释放对象内存。

独立生命周期:每个shared_ptr实例是独立的,但其内部的控制块指针指向同一内存管理单元。因此,即使某个shared_ptr被销毁,只要引用计数未归零,对象仍存在

控制块独立分配:控制块与原始对象通常是分离存储的(除非使用std::make_shared合并优化)。这种设计使得即使所有shared_ptr被销毁,控制块仍可独立管理弱引用计数和删除逻辑

二、进阶

1、智能指针相互间赋值

1.1 unique_ptr 之间的赋值

左值不能直接赋值unique_ptr 表示独占所有权,禁止普通左值之间的赋值,避免多个指针指向同一对象。

unique_ptr<int> a = make_unique<int>(5);  
unique_ptr<int> b;  
b = a; // 编译错误,a 是左值,直接赋值违反独占原则  

右值可通过移动赋值:若为右值(如临时对象或通过 std::move 转换),则可移动赋值。此时原 unique_ptr 失去所有权(变为 nullptr) 

unique_ptr<int> a = make_unique<int>(5);  
unique_ptr<int> b = std::move(a); // 合法,a 变为 nullptr,b 接管对象 
1.2 unique_ptr、shared_ptr 的赋值 

unique_ptr 右值可赋值给 shared_ptrshared_ptr 有显式构造函数,可接收 unique_ptr 右值(如临时对象或 std::move 转换后的对象)。此时 shared_ptr 接管对象所有权:

unique_ptr<int> uptr = make_unique<int>(10);  
shared_ptr<int> sptr1 = std::move(uptr); // 合法,uptr 变为 nullptr  

shared_ptr<int> sptr2(make_unique<int>(20)); // 合法,临时 unique_ptr 作为右值  
shared_ptr<int> sptr3 = make_unique<int>(42); // 隐式移动构造。在构造shared_ptr时传入unique_ptr的右值(如函数返回值),编译器会自动完成移动操作

unique_ptr 左值不可直接赋值给 shared_ptr:若直接用 unique_ptr 左值初始化 shared_ptr,会编译错误 

unique_ptr<int> uptr = make_unique<int>(10);  
shared_ptr<int> sptr3(uptr); // 编译错误,uptr 是左值  
1.3 总结
  • 允许转换的方向unique_ptr → shared_ptr(通过移动语义)。
  • 禁止转换的方向shared_ptr → unique_ptr
  • unique_ptr 之间仅允许右值移动赋值;unique_ptr 可通过右值(如 std::move 或临时对象)赋值给 shared_ptr,但左值不行。 

2、  shared_ptr<int*>有什么问题?

// 正确用法:shared_ptr<int> 管理 int 对象  
shared_ptr<int> sp1 = make_shared<int>(10); // 直接管理 int 值 10  

// 非常规用法:shared_ptr<int*> 管理 int* 指针
// 管理 int*,需确保 int* 指向的内存正确释放(此处虽合法但不推荐)    
shared_ptr<int*> sp2 = make_shared<int*>(new int(20)); 

shared_ptr<int>:直接管理一个 int 类型的对象,智能指针内部存储的是 int 对象的指针(如 int*),并负责该 int 对象的内存释放。符合智能指针的设计初衷,直接管理对象内存。

shared_ptr<int*>:管理一个 int* 类型的指针(即指针本身成为智能指针管理的对象)。这种用法违背了智能指针简化对象管理的原则,因为 int* 指向的对象仍需确保其生命周期正确(虽然此处 int* 由 new int 分配,delete 合法,但代码逻辑易混淆)。

std::shared_ptr<int*> sptr(
    new int*(new int(42)), 
    [](int** p) { 
        delete *p;  // 释放内层 int 对象
        delete p;    // 释放外层 int* 指针
    }
);

需在自定义删除器中显式释放内层内存。


使用 shared_ptr<T[]>(C++17 及以上)

int main() {
    // 创建 shared_ptr 管理动态数组(C++17 起支持)
    std::shared_ptr<int[]> arr(new int[5]{1, 2, 3, 4, 5});

    // 直接通过 operator[] 访问元素
    for (int i = 0; i < 5; ++i) {
        arr[i] = i * 10;        // 修改元素
        std::cout << arr[i] << " ";  // 输出:0 10 20 30 40
    }

    // 无需手动释放,shared_ptr 自动调用 delete[]
    return 0;
}

自定义删除器(兼容 C++11/14) 

int main() {
    // 定义删除器(Lambda 表达式)
    auto array_deleter = [](int* ptr) {
        delete[] ptr;  // 必须显式调用 delete[]
        std::cout << "动态数组内存已释放\n";
    };

    // 创建 shared_ptr 并传入删除器
    std::shared_ptr<int> arr(new int[5]{1, 2, 3, 4, 5}, array_deleter);

    // 通过 get() 获取原始指针访问元素
    int* raw_ptr = arr.get();
    for (int i = 0; i < 5; ++i) {
        raw_ptr[i] = i * 10;       // 修改元素
        std::cout << raw_ptr[i] << " ";  // 输出:0 10 20 30 40
    }

    // 析构时自动调用 array_deleter
    return 0;
}

三、其他

警告:
永远不要将资源分配结果赋值给原始指针。无论使用哪种资源分配方法,都应当立即将资源指针存储在智能指针 unique_ptr 或 shared_ptr 中,或使用其他 RAII 类。RAII 代表 Resource Acquisition Is Initialization (资源获取即初始化)。RAII 类获取某个资源的所有权,并在适当的时候进行释放。—— 《C++20高级编程(上册)》

出现这个错误的原因是在导入seaborn包时,无法从typing模块中导入名为'Protocol'的对象。 解决这个问题的方法有以下几种: 1. 检查你的Python版本是否符合seaborn包的要求,如果不符合,尝试更新Python版本。 2. 检查你的环境中是否安装了typing_extensions包,如果没有安装,可以使用以下命令安装:pip install typing_extensions。 3. 如果你使用的是Python 3.8版本以下的版本,你可以尝试使用typing_extensions包来代替typing模块来解决该问题。 4. 检查你的代码是否正确导入了seaborn包,并且没有其他导入错误。 5. 如果以上方法都无法解决问题,可以尝试在你的代码中使用其他的可替代包或者更新seaborn包的版本来解决该问题。 总结: 出现ImportError: cannot import name 'Protocol' from 'typing'错误的原因可能是由于Python版本不兼容、缺少typing_extensions包或者导入错误等原因造成的。可以根据具体情况尝试上述方法来解决该问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [ImportError: cannot import name ‘Literal‘ from ‘typing‘ (D:\Anaconda\envs\tensorflow\lib\typing....](https://blog.youkuaiyun.com/yuhaix/article/details/124528628)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值