C++ 智能指针,高频面试点整理

  1. 动态内存的问题/为什么引入智能指针?
  2. 和裸指针相比有什么优点?
  3. share_ptr、unique_ptr、weak 的区别是什么?
  4. 有没有看过shared的实现?weak解决循环引用
  5. 如果函数内部有一个unique,指向一个对象,那可以return unique吗?
  6. 同一个对象从unique_ptrA转移到unique_ptrB 怎么做?

什么是动态内存?

动态内存指程序运行时在堆(heap)上按需分配的内存,与编译时或函数调用时就确定好大小的“静态”或“自动”内存不同。

动态内存的问题

可以处理不确定大小或需要灵活管理内存,但也会带来问题,比如分配内存忘记释放,导致内存泄露;指针还在使用就被释放了,非法访问异常;对象的声明周期难以明确。

sahred、unique、weak 的区别

智能指针的作用就是管理一个指针,实际上就是一个类,超出类的作用域时,调用析构函数自动释放资源。C++中有shared、unique、weak三种指针。shared允许多个指针指向同一对象,而unique独占对象,weak的话更像是shared的辅助,解决shared的循环引用问题。一个weak绑定到一个shared不会改变shared_ptr的引用计数。

shared原理

首先是定义了一个基类,shared和unique都继承这个基类,但shared还继承了一个引用计数的基类,这个引用计数基类提供了一个引用计数机制,类里面有一个成员变量,这个成员变量记录资源的引用计数,或者叫强引用计数。表示有多少个指针指向该资源,(还有一个是弱引用计数)。当引用计数减少到0时,销毁资源(并减少弱引用计数,弱引用计数到0时,就会删除计数的基类)。

weak解决循环引用

循环引用是指两个或多个对象之间相互引用,形成一个环状结构。在使用智能指针时,特别是 std::shared_ptr 时,如果不注意管理对象之间的引用关系,就有可能出现循环引用,导致内存泄漏。这个循环引用和死锁有点类似,也是成环了,A、B相互引用,强引用计数没办法为0,资源就不能被释放,代码示例如下:

#include <iostream>
#include <memory>

class B;

class A {
public:
    std::shared_ptr<B> b_ptr;
    A() {
        std::cout << "A constructor" << std::endl;
    }
    ~A() {
        std::cout << "A destructor" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> a_ptr;
    B() {
        std::cout << "B constructor" << std::endl;
    }
    ~B() {
        std::cout << "B destructor" << std::endl;
    }
};
int main() {
    std::shared_ptr<A> a_ptr = std::make_shared<A>();
    std::shared_ptr<B> b_ptr = std::make_shared<B>();

    // 循环引用
    a_ptr->b_ptr = b_ptr;
    b_ptr->a_ptr = a_ptr;

    return 0;
}

在这个示例中,类 A 和类 B 分别拥有一个 std::shared_ptr 成员,分别指向对方的对象。当 main 函数中创建了 A 和 B 的 shared_ptr 对象后,A 和 B 对象之间建立了循环引用关系。由于 shared_ptr 使用引用计数来管理内存,这种循环引用会导致对象的引用计数永远不会为零。

 std::cout <<a_ptr.use_count() //打印引用计数查看 

解决办法就是将两个类中的一个成员变量改为weak_ptr对象。

weak解决空悬指针问题

有两个指针p1和p2,指向堆上的同一个对象Object,p1和p2位于不同的线程中。假设线程A通过p1指针将对象销毁了(尽管把p1置为了NULL),那p2就成了空悬指针。

weak不控制对象的生命期,但是它知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared(提升操作通过lock()函数获取所管理对象的强引用指针);如果对象已经死了,提升会失败,返回一个空的shared。

函数内部创建 unique,能否直接作为返回值返回?

可以。unique 支持移动语义(std::move),函数返回时会把内部的指针“搬走”给调用者。

std::unique_ptr<MyClass> makeObject() {
    auto ptr = std::make_unique<MyClass>(/*ctor args*/);
    return ptr;              // C++17 起可直接 return ptr; 
    // 或者写作 return std::move(ptr); 在 C++11/14 中同样有效
}

// 调用方
auto obj = makeObject();    // obj 接管了 ptr 原来管理的对象

如何将同一个对象从 unique_ptr A 转移到 unique_ptr B

使用 std::move(或 release()/reset() 组合)将所有权移动。

std::unique_ptr<MyClass> A = std::make_unique<MyClass>();
// 转移所有权:A 变空(nullptr),B 管理原对象
std::unique_ptr<MyClass> B = std::move(A);

// 此后:A.get() == nullptr,B.get() 指向原对象
std::unique_ptr<MyClass> A = std::make_unique<MyClass>();
std::unique_ptr<MyClass> B; 
B.reset(A.release());  // release() 交出原始指针,A 置空,reset() 接管

两种方式本质上都是将底层原始指针的所有权从 A 转到 B,且 A 最终都变成了空指针,但是更推荐方案 1,因为方案 1 语义清晰,而且:

  • std::move 是原子操作,只会修改内部指针和计数,不会抛出异常;
  • release()/reset() 需要先调用 release() 得到裸指针,若后续发生异常(如在 reset() 之前抛),就会造成内存泄漏。

关于 std::move 在后续 C++11 新特性中有介绍。

#嵌入式面经##嵌入式##C++#
嵌入式软件面试笔记 文章被收录于专栏

整理高频考点、汇总常见的面试题库、总结面试中的提问 每个知识点尽可能做到「 原理 → 扩展 」

全部评论

相关推荐

04-27 20:48
已编辑
门头沟学院 Java
点赞 评论 收藏
分享
评论
3
6
分享

创作者周榜

更多
牛客网
牛客企业服务