面试题答案
一键面试1. unique_ptr 和 shared_ptr 的特点
- unique_ptr:
- 独占所有权:一个
unique_ptr
拥有对对象的唯一所有权,同一时刻只能有一个unique_ptr
指向给定对象。当unique_ptr
被销毁时,它所指向的对象也会被销毁。 - 不能复制:
unique_ptr
不能进行复制构造和赋值操作,但可以进行移动构造和移动赋值,这使得它在对象所有权转移时非常高效。 - 轻量级:
unique_ptr
通常实现得非常轻量级,只包含一个指针成员,没有额外的引用计数开销。
- 独占所有权:一个
- shared_ptr:
- 共享所有权:多个
shared_ptr
可以指向同一个对象,通过引用计数来管理对象的生命周期。当最后一个指向对象的shared_ptr
被销毁时,对象才会被释放。 - 可复制:
shared_ptr
支持复制构造和赋值操作,每次复制都会增加引用计数,每次销毁都会减少引用计数。 - 线程安全:在多线程环境下,
shared_ptr
的引用计数操作是线程安全的,但对象的访问可能仍需要额外的同步机制。
- 共享所有权:多个
2. 内存管理机制
- unique_ptr:
- 当
unique_ptr
对象生命周期结束(例如离开作用域)时,它会自动调用其析构函数。在析构函数中,unique_ptr
会检查它是否拥有对象(即指针是否非空),如果是,则调用delete
来释放对象的内存。 - 对于数组类型,
unique_ptr
提供了unique_ptr<T[]>
的特化版本,它会在析构时调用delete[]
来释放数组内存。 - 示例代码:
- 当
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed" << std::endl; }
~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};
int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
// 离开作用域时,ptr1 会自动销毁 MyClass 对象
return 0;
}
- shared_ptr:
shared_ptr
使用引用计数来跟踪指向对象的shared_ptr
数量。每次创建一个新的shared_ptr
指向对象(例如通过复制构造或赋值),引用计数就会增加;每次shared_ptr
被销毁,引用计数就会减少。- 当引用计数降为 0 时,
shared_ptr
会自动调用delete
来释放对象的内存。此外,shared_ptr
还可以自定义删除器,用于在引用计数为 0 时执行特定的清理操作。 - 示例代码:
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed" << std::endl; }
~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
{
std::shared_ptr<MyClass> ptr3 = ptr1; // 引用计数再次增加
} // ptr3 离开作用域,引用计数减少
// ptr1 和 ptr2 离开作用域时,引用计数减为 0,MyClass 对象被销毁
return 0;
}
3. 性能差异
- unique_ptr:
- 由于
unique_ptr
不涉及引用计数的开销,因此在性能上通常更高效,特别是在对象所有权频繁转移的场景下,因为移动操作的开销很小。 - 它适合于对象所有权明确且单一的情况,例如函数内部局部对象的管理,不需要额外的引用计数维护成本。
- 由于
- shared_ptr:
shared_ptr
的引用计数操作会带来一定的性能开销,包括增加、减少引用计数以及处理引用计数为 0 时的内存释放操作。- 在多线程环境下,由于引用计数操作需要保证线程安全,可能会涉及到原子操作,这进一步增加了开销。然而,在需要共享对象所有权的场景下,
shared_ptr
提供了方便的内存管理机制,尽管有性能损失,但可以简化代码逻辑。
4. 将 unique_ptr 转换为 shared_ptr 的情况及方法
- 转换情况:
- 当原本由
unique_ptr
独占管理的对象需要被多个地方共享时,就需要将unique_ptr
转换为shared_ptr
。例如,在函数内部创建了一个unique_ptr
管理的对象,现在需要将这个对象传递给多个不同的函数或模块,且这些函数或模块都需要在对象的生命周期内共享该对象,此时就需要进行转换。
- 当原本由
- 转换方法:
- 可以通过
std::shared_ptr
的构造函数来将unique_ptr
转换为shared_ptr
。std::shared_ptr
提供了一个构造函数,接受一个std::unique_ptr
参数,这种转换是安全有效的,因为unique_ptr
会将对象的所有权转移给shared_ptr
。 - 示例代码:
- 可以通过
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed" << std::endl; }
~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};
std::shared_ptr<MyClass> getSharedPtr() {
std::unique_ptr<MyClass> uniquePtr = std::make_unique<MyClass>();
// 将 unique_ptr 转换为 shared_ptr
std::shared_ptr<MyClass> sharedPtr = std::shared_ptr<MyClass>(std::move(uniquePtr));
return sharedPtr;
}
int main() {
std::shared_ptr<MyClass> ptr = getSharedPtr();
// 可以在其他地方继续使用 ptr 共享 MyClass 对象
return 0;
}
在上述代码中,getSharedPtr
函数内部创建了一个 unique_ptr
,然后通过 std::shared_ptr
的构造函数将其转换为 shared_ptr
,并返回 shared_ptr
。std::move
用于将 unique_ptr
的所有权转移给 shared_ptr
,确保转换过程的正确性。