面试题答案
一键面试std::unique_ptr原理及实现机制
- 原理:
std::unique_ptr
采用独占式所有权模型。一个std::unique_ptr
对象拥有对其所指向资源的唯一所有权。当std::unique_ptr
对象被销毁时(例如离开其作用域),它所指向的资源也会被自动释放。这就确保了在任何时刻,只有一个指针可以访问该资源,从而避免了悬空指针的产生。因为当指向资源的std::unique_ptr
被销毁,资源也被释放,不存在其他指针还能指向已释放资源的情况。
- 实现机制:
std::unique_ptr
通常通过一个裸指针来存储所指向的对象,并在其析构函数中释放该对象。例如,对于std::unique_ptr<int>
,内部可能有一个int*
类型的成员变量来存储指向int
对象的地址。当std::unique_ptr
对象生命周期结束,其析构函数会调用delete
操作符来释放该int
对象。它不支持拷贝构造和赋值操作符(普通的拷贝和赋值),以保证所有权的唯一性,但支持移动语义。移动语义允许将std::unique_ptr
的所有权从一个对象转移到另一个对象,例如std::unique_ptr<int> p1 = std::make_unique<int>(5); std::unique_ptr<int> p2 = std::move(p1);
,此时p1
不再拥有对象,p2
拥有原来p1
所指向的对象。
std::shared_ptr原理及实现机制
- 原理:
std::shared_ptr
采用引用计数的方式来管理资源。多个std::shared_ptr
对象可以指向同一个资源,每个std::shared_ptr
对象内部都维护一个引用计数,记录当前有多少个std::shared_ptr
指向该资源。当一个std::shared_ptr
对象被创建并指向某个资源时,引用计数加1;当一个std::shared_ptr
对象被销毁(例如离开作用域),引用计数减1。当引用计数变为0时,意味着没有任何std::shared_ptr
再指向该资源,此时资源会被自动释放。这样就避免了悬空指针,因为只要有std::shared_ptr
指向资源,资源就不会被释放,也就不会出现悬空指针。
- 实现机制:
std::shared_ptr
内部除了包含一个指向资源的指针外,还包含一个指向控制块的指针。控制块中存储着引用计数以及可能的弱引用计数(用于std::weak_ptr
)等信息。每次创建、拷贝或销毁std::shared_ptr
对象时,相应地调整引用计数。例如std::shared_ptr<int> p1 = std::make_shared<int>(5); std::shared_ptr<int> p2 = p1;
,此时p1
和p2
都指向同一个int
对象,控制块中的引用计数变为2。当p1
或p2
其中一个离开作用域时,引用计数减1,只有当引用计数变为0时,才会释放int
对象所占用的内存。
使用场景差异
- std::unique_ptr:
- 适合独占资源场景:当一个资源只应由一个对象管理其生命周期时,
std::unique_ptr
是很好的选择。例如,一个函数内部创建了一个动态分配的对象,并且不希望将该对象的所有权传递出去,就可以使用std::unique_ptr
。另外,对于一些性能敏感且资源只在局部使用的场景,std::unique_ptr
由于没有引用计数的开销,性能更优。比如一个只在函数内部短暂使用的大型动态数组。 - 用于移动语义场景:在需要将资源的所有权从一个对象转移到另一个对象时,
std::unique_ptr
的移动语义能高效地完成此操作,避免不必要的拷贝。例如,在函数返回一个动态分配的对象时,使用std::unique_ptr
可以通过移动语义高效返回对象,而不进行拷贝。
- 适合独占资源场景:当一个资源只应由一个对象管理其生命周期时,
- std::shared_ptr:
- 适合共享资源场景:当多个对象需要共享同一个资源的所有权时,
std::shared_ptr
是首选。比如在实现一个多线程环境下的共享数据结构,多个线程可能都需要访问和管理这个数据结构,std::shared_ptr
可以确保只要还有线程在使用该数据结构,它就不会被释放。 - 需要复杂对象生命周期管理场景:如果对象的生命周期依赖于多个不同的对象,
std::shared_ptr
通过引用计数可以更好地管理这种复杂的依赖关系。例如,一个对象A在不同的模块中被多个对象引用,使用std::shared_ptr
可以自动处理对象A的释放,而不需要手动跟踪所有引用对象的生命周期。
- 适合共享资源场景:当多个对象需要共享同一个资源的所有权时,