当前位置: 首页 > web >正文

C++智能指针概念理解的面试题

C++智能指针概念理解的面试题

第一部分:基础概念
  1. 解释std::unique_ptrstd::shared_ptr在以下方面的区别:
    • 所有权语义
    • 性能开销
    • 自定义删除器的存储方式
    • 是否支持数组类型

    答案:

    所有权语义:

    unique_ptr:独占所有权,不能复制,只能移动shared_ptr:共享所有权,通过引用计数管理,可以复制
    

    性能开销:

    unique_ptr:几乎无额外开销(等同于原始指针)shared_ptr:有控制块和引用计数的开销
    

    自定义删除器的存储方式:

    unique_ptr:删除器是类型的一部分,直接存储在对象中(无额外开销)shared_ptr:删除器存储在控制块中(类型擦除,有额外开销)
    

    是否支持数组类型:

    unique_ptr:通过unique_ptr<T[]>显式支持数组shared_ptr:不直接支持数组,需自定义删除器
    
  2. 考虑以下代码:
std::shared_ptr<int> p1(new int(42));
std::shared_ptr<int> p2 = std::make_shared<int>(42);
  • 这两种初始化方式在内存分配上有何不同?
  • 为什么推荐使用make_shared
  • 在什么情况下不能使用make_shared

答案:

内存分配差异

  • shared_ptr<int> p1(new int(42)):两次分配(对象和控制块分开)
  • make_shared<int>(42):一次分配(对象和控制块合并)

推荐make_shared的原因

  1. 更高性能(单次分配)
  2. 更安全(避免内存泄漏)
  3. 更好的缓存局部性

不能使用make_shared的情况

  1. 需要自定义删除器
  2. 需要大括号初始化
  3. 需要weak_ptr长期存在而对象可被释放的场景
第二部分:深入实现
  1. 假设C++标准库中没有提供std::weak_ptr,你如何仅使用std::shared_ptr来实现一个弱引用指针?请描述你的设计方案,包括:
    • 如何检测所指向的对象是否已被释放
    • 如何实现lock()操作来获取可用的shared_ptr
    • 如何避免循环引用

答案:

设计方案

  1. 使用shared_ptr的引用计数结构扩展
  2. 添加"弱引用计数"跟踪观察者数量
  3. 对象释放条件:强引用=0(无论弱引用)

实现lock()

  1. 检查强引用计数>0
  2. 如果对象存在,增加强引用计数并返回新shared_ptr
  3. 否则返回空shared_ptr

避免循环引用

  1. 弱引用不参与对象生命周期管理

  2. 仅当强引用=0时才释放对象

  3. 控制块在弱引用=0时才释放

  4. 解释std::shared_ptr的引用计数机制:
  • 控制块(control block)通常包含哪些信息?
  • 在多线程环境下,引用计数如何保证原子性?
  • 为什么std::shared_ptr的引用计数使用原子操作而不是简单的整数?

答案:

控制块内容

  1. 强引用计数
  2. 弱引用计数
  3. 自定义删除器
  4. 分配器(如使用)
  5. 指向被管理对象的指针

原子性保证

  1. 使用原子操作(如std::atomic)修改引用计数
  2. 内存序保证(通常memory_order_relaxed用于计数)
  3. 控制块本身线程安全

使用原子操作的原因

  1. 多线程环境下安全修改计数

  2. 避免数据竞争

  3. 保证内存可见性

第三部分:高级应用
  1. 考虑以下自定义删除器的使用场景:
void file_deleter(FILE* fp) {std::fclose(fp);
}std::unique_ptr<FILE, decltype(&file_deleter)> fp(std::fopen("test.txt", "r"), file_deleter);
  • 为什么这里decltype(&file_deleter)是必要的?
  • 如果改用lambda表达式作为删除器,代码应该如何修改?
  • 比较std::unique_ptrstd::shared_ptr在自定义删除器存储方式上的差异

答案:

decltype(&file_deleter)必要性

  • unique_ptr的删除器是类型的一部分
  • 必须明确指定删除器类型

lambda删除器

auto deleter = [](FILE* fp) { std::fclose(fp); };
std::unique_ptr<FILE, decltype(deleter)> fp(std::fopen("test.txt", "r"), deleter);

存储方式差异

  • unique_ptr:删除器作为模板参数,直接存储

  • shared_ptr:删除器类型擦除,存储在控制块

  1. 设计一个支持多态对象的安全删除方案:
struct Base { virtual ~Base() = default; };
struct Derived : Base { /*...*/ };std::shared_ptr<Base> p = std::make_shared<Derived>();
  • 解释为什么这个方案是类型安全的
  • 如果Base的析构函数不是虚函数,会发生什么?
  • 如何设计一个工厂函数,返回基类指针但能正确删除派生类对象?

答案:

类型安全原因

  1. make_shared创建完整派生类对象
  2. 虚析构函数确保正确析构顺序
  3. 共享指针保持完整类型信息

非虚析构函数问题

  1. 派生类部分不会被析构
  2. 资源泄漏
  3. 未定义行为

工厂函数设计

template<typename Derived, typename... Args>
std::shared_ptr<Base> create(Args&&... args) {return std::shared_ptr<Base>(new Derived(std::forward<Args>(args)...));
}
第四部分:陷阱与最佳实践
  1. 分析以下代码的问题:
std::shared_ptr<int> create_shared() {int* raw = new int(42);std::shared_ptr<int> p1(raw);std::shared_ptr<int> p2(raw);return p1;
}
  • 这段代码会导致什么问题?
  • 如何修改才能使其安全?
  • 解释为什么std::enable_shared_from_this能解决类似问题

答案:

问题

  1. 两个shared_ptr独立管理同一原始指针
  2. 会导致双重释放
  3. 引用计数不共享

修改方案

std::shared_ptr<int> create_shared() {auto p1 = std::make_shared<int>(42);std::shared_ptr<int> p2 = p1;  // 共享所有权return p1;
}

enable_shared_from_this作用

  1. 允许对象安全地生成指向自身的shared_ptr

  2. 内部使用weak_ptr避免循环引用

  3. 确保所有shared_ptr共享同一控制块

  4. 讨论在以下场景中智能指针的使用策略:

  • 作为类成员变量
  • 在容器中存储动态分配的对象
  • 跨线程传递对象所有权
  • 与第三方C库交互

答案:

类成员变量

  • 优先unique_ptr表达独占
  • 需要共享时用shared_ptr
  • 观察用weak_ptr

容器存储

  • 优先unique_ptr(明确所有权)
  • 需要共享时用shared_ptr
  • 考虑vector<unique_ptr>替代vector<Base*>

跨线程传递

  1. shared_ptr引用计数线程安全
  2. 对象本身需额外同步
  3. 避免跨线程传递原始指针

与C库交互

  1. 自定义删除器包装C接口
  2. unique_ptr管理C资源
  3. 注意所有权转移语义
http://www.xdnf.cn/news/2080.html

相关文章:

  • window.location.href的用法
  • 基于 Netmiko 的网络设备自动化操作
  • 《逐梦九天:中国航天编年史》
  • QT文本框(QTextEdit)设置内容只可复制粘贴
  • C++:继承机制详解
  • Cursor 配置 MCP Tool
  • 写在后面的话
  • yolo常用操作(长话短说)热力图,特征图,结构图,训练,测试,预测
  • 打开Qt应用程序以控制台
  • Linux基础篇、第四章_02磁盘及分区管理fdisk 和 gdisk
  • 厚铜PCB打样全流程解析:从文件审核到可靠性测试的关键步骤
  • Python的库
  • Hbase集群管理与实践
  • C语言——字串处理
  • 什么是快应用
  • STM32 I2C总线通信协议
  • 遥感金融风险监管:技术革新与实践探索
  • Java—— 常见API介绍 第五期
  • cursor 提示词和规则
  • 基于SpringBoot+Vue实现停车场管理系统
  • sync.Cond条件变量:使用场景与范例
  • Centos 7 ssh连接速度慢(耗时秒+)
  • LWIP中两种重要的数据结构pbuf和pcb详细介绍
  • 【大模型学习】Qwen-2.5-VL制作gradio前端demo页面
  • (九)深入了解AVFoundation-采集:拍照 摄像头切换、拍照参数和照片数据EXIF 信息
  • Pandas 数据处理:长格式到宽格式的全面指南
  • 文章记单词 | 第41篇(六级)
  • Vue3文件上传组件实战:打造高效的Element Plus上传解决方案,可以对文件进行删除,查看,下载功能。
  • 代码随想录算法训练营第五十八天 | 1.拓扑排序精讲 2.dijkstra(朴素版)精讲 卡码网117.网站构建 卡码网47.参加科学大会
  • 【基于Qt的QQ音乐播放器开发实战:从0到1打造全功能音乐播放应用】