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

发布于:2025-04-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

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

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

一、入门

1、unique_ptr 和 shared_ptr 默认能否管理动态数组?需要注意什么?

unique_ptr:可通过特化删除器管理数组,需显式指定delete[]

std::unique_ptr<int[]> arr(new int[5]{1,2,3,4,5});

shared_ptr:默认使用delete而非delete[],直接管理数组会导致未定义行为。需自定义删除器

std::shared_ptr<int> arr(new int[5], [](int* p) { delete[] p; });

2、当容器中存储智能指针时,需要注意什么?

  • 容器的拷贝 / 移动操作会触发智能指针的拷贝 / 移动语义,引用计数自动管理。
  • 避免存储悬空指针:确保容器中的智能指针生命周期正确。
std::vector<std::shared_ptr<int>> vec;
vec.push_back(std::make_shared<int>(42));
  • shared_ptr 拷贝语义:容器拷贝时会增加引用计数,多个容器共享同一资源
  • unique_ptr 移动语义:移动后原容器指针变为 nullptr,避免重复释放
std::vector<std::shared_ptr<int>> vec1 = {std::make_shared<int>(42)};
auto vec2 = vec1; // 引用计数变为2
std::vector<std::unique_ptr<int>> vec3 = {std::make_unique<int>(42)};
auto vec4 = std::move(vec3); // vec3变为空

3、为什么 auto_ptr被废弃

  • 不安全的所有权转移:auto_ptr通过拷贝构造或赋值转移所有权,原指针变为空,若后续误用会导致未定义行为。
  • ​与容器不兼容:auto_ptr无法存入STL容器,因容器元素需支持拷贝,而auto_ptr的拷贝会转移所有权
std::auto_ptr<int> p1(new int(5));
std::auto_ptr<int> p2 = p1; // p1变为空指针
std::cout << *p1; // 运行时崩溃

 4、std::make_sharedstd::make_unique的额外优势

  • 异常安全性:若new与shared_ptr构造分离,中间发生异常会导致内存泄漏,而make_shared是原子操作。
  • ​内存优化:make_shared将对象与控制块合并分配,减少内存碎片(make_unique在C++14引入)

二、进阶

1、​shared_ptr 的默认删除器和自定义删除器在内存管理上有何不同?

  • 默认删除器:使用delete释放对象,无法处理非堆内存(如文件句柄)
  • 自定义删除器:可释放任意资源(如文件、网络连接),且不影响引用计数的线程安全。
// 自定义删除器释放文件句柄
std::shared_ptr<FILE> file(fopen("test.txt", "r"), [](FILE* f) { fclose(f); });

2、当智能指针所在函数抛出异常时,资源是否会被正确释放?

会被正确释放。智能指针利用 RAII 机制,在离开作用域时(包括异常场景)自动释放资源,避免内存泄漏。

void test() {
    auto ptr = std::make_shared<int>(42);
    throw std::runtime_error("Exception!"); // ptr会被正确释放
}

3、混合使用智能指针和原始指针可能导致什么问题? 

悬空指针:若原始指针指向的对象被智能指针释放,后续使用原始指针会导致未定义行为

int* raw = new int(10);
std::shared_ptr<int> sp(raw);
delete raw; // sp变为悬空指针

重复释放:多个智能指针管理同一原始指针(如p2(raw))会导致重复释放。

4、 循环引用如何解决?

场景:父子节点互相持有shared_ptr导致引用计数永不为零。

​解决方案:将其中一个指针改为weak_ptr,如树形结构的父节点用weak_ptr,子节点用shared_ptr

class Node {
public:
    std::shared_ptr<Node> child;
    std::weak_ptr<Node> parent; // 打破循环
};

三、高阶

1、如何安全地在基类和派生类之间使用智能指针?

使用std::dynamic_pointer_cast进行安全的向下转型

class Base {};
class Derived : public Base {};

// 创建子类实体,赋值给基类指针
std::shared_ptr<Base> base = std::make_shared<Derived>();
// 指向子类实体的基类指针,还原回子类
std::shared_ptr<Derived> derived = std::dynamic_pointer_cast<Derived>(base);
if (derived) { /* 安全使用 */ }

dynamic_pointer_cast 的安全转型,必须用于 shared_ptr 之间的转型 

std::shared_ptr<Base> base = std::make_shared<Derived>();
if (auto derived = std::dynamic_pointer_cast<Derived>(base)) {
    // 安全使用
}

 使用static_pointer_castdynamic_pointer_cast进行类型转换,保留引用计数

2、shared_ptr 的引用计数如何保证线程安全? 

  • 使用原子操作(如std::atomic<int>)或互斥锁保护引用计数。
  • C++ 标准库中的std::shared_ptr保证引用计数操作的原子性,但对象访问仍需额外同步。

3、 shared_ptr 的性能瓶颈是什么?如何优化?

  • 开销:引用计数的原子操作和控制块的内存分配。
  • 优化
    • 使用std::make_shared减少内存分配次数。一次分配控制块和对象内存,减少内存碎片,提升缓存利用率
    • 避免在性能关键路径中频繁操作引用计数。
    • 对性能要求极高时,可考虑对象池或手动管理资源。

4、 智能指针与 RAII 类、对象池、裸指针相比,各自的优缺点是什么?

  • 智能指针:自动管理内存,避免泄漏,但有额外开销。
  • RAII 类:可管理任意资源,但需自定义类。
  • 对象池:减少内存分配次数,适合高频创建销毁的场景
  • 裸指针:灵活但易出错,需手动管理。

5、可以在构造函数中调用shared_from_this() 吗?

不能,因对象尚未被shared_ptr管理,引发std::bad_weak_ptr异常

​正确用法:仅在对象已被shared_ptr管理后调用,如在成员函数中。

class Worker : public std::enable_shared_from_this<Worker> {
public:
    void start() {
        auto self = shared_from_this(); // 安全
    }
    // 构造函数中调用会导致异常!
};

四、其他

1、为什么 unique_ptr 只能通过std::move转移所有权?

  • 禁止拷贝以确保独占性,std::move显式转移所有权,避免隐式错误。

2、智能指针类型选择

工厂模式用 unique_ptr,缓存用 shared_ptr

3、通过get()获取裸指针后,若其生命周期长于shared_ptr,会导致悬空指针

std::shared_ptr<int> sp = std::make_shared<int>(42);
int* raw = sp.get();
sp.reset(); // raw成为悬空指针

4、误用weak_ptr:未检查lock()结果直接使用,引发访问已释放对象。 


网站公告

今日签到

点亮在社区的每一天
去签到