C++ 智能指针,高频面试点整理
- 动态内存的问题/为什么引入智能指针?
- 和裸指针相比有什么优点?
- share_ptr、unique_ptr、weak 的区别是什么?
- 有没有看过shared的实现?weak解决循环引用
- 如果函数内部有一个unique,指向一个对象,那可以return unique吗?
- 同一个对象从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 新特性中有介绍。
整理高频考点、汇总常见的面试题库、总结面试中的提问 每个知识点尽可能做到「 原理 → 扩展 」