面试题答案
一键面试std::unique_ptr确保异常安全分析
- 构造函数
std::unique_ptr
的构造函数通常是无异常抛出的。例如,std::unique_ptr<int> ptr(new int(5));
在new int(5)
成功分配内存后,std::unique_ptr
的构造函数会接管这个指针的所有权。如果new int(5)
抛出异常(比如内存分配失败),std::unique_ptr
不会持有一个无效指针,因为它的构造函数还未执行完成,也就不存在资源泄漏问题,符合异常安全的基本要求。
- 析构函数
std::unique_ptr
的析构函数会自动释放它所管理的资源。当std::unique_ptr
对象生命周期结束时(无论是正常结束还是因为异常导致栈展开),析构函数会被调用。例如:
{ std::unique_ptr<int> ptr(new int(5)); } // 这里`ptr`的析构函数会被调用,释放`new int(5)`分配的内存
- 由于析构函数是自动调用且释放资源的操作是确定的,不会抛出异常,所以在异常发生导致栈展开时,资源能够被正确释放,满足异常安全中的“无泄漏”原则。
- 赋值操作
- 对于
std::unique_ptr
的赋值操作(operator =
),它会先释放当前所管理的资源(如果有),然后接管新的指针。例如:
std::unique_ptr<int> ptr1(new int(5)); std::unique_ptr<int> ptr2(new int(10)); ptr1 = std::move(ptr2);
- 在
ptr1 = std::move(ptr2)
中,ptr1
会先释放它原来管理的int(5)
(如果有),然后接管ptr2
的指针。这个过程中,如果接管新指针时抛出异常(比如在某些自定义的资源接管逻辑中),ptr1
会恢复到赋值前的状态(持有原来的资源或为nullptr
),而ptr2
会释放资源(因为已经std::move
了),符合异常安全中的“基本保证”原则(操作失败时,对象处于有效但未指定的状态)。
- 对于
类A中std::unique_ptr处理符合异常安全原则
- 拷贝构造函数
std::unique_ptr
是不可拷贝的,因为它的设计理念就是独占资源。如果类A
中有一个std::unique_ptr
成员变量,在类A
的拷贝构造函数中不能简单地拷贝std::unique_ptr
。例如:
class A { private: std::unique_ptr<int> data; public: A(const A& other) = delete; // 显式删除拷贝构造函数,避免意外拷贝导致资源管理问题 };
- 如果确实需要拷贝资源,应该重新分配内存并拷贝数据。例如:
class A { private: std::unique_ptr<int> data; public: A(const A& other) { if (other.data) { data.reset(new int(*other.data)); } } };
- 在这个过程中,如果
new int(*other.data)
抛出异常,data
不会被赋值,对象A
不会处于无效状态,符合异常安全的“基本保证”原则。
- 移动构造函数
- 在类
A
的移动构造函数中,std::unique_ptr
的处理应该是安全的。例如:
class A { private: std::unique_ptr<int> data; public: A(A&& other) noexcept : data(std::move(other.data)) {} };
- 这里使用
std::move
来转移std::unique_ptr
的所有权。由于std::unique_ptr
的移动构造函数是noexcept
的,不会抛出异常,所以在移动过程中如果发生异常(在移动构造函数外部),other
的资源已经被转移,data
持有正确的资源,符合异常安全原则。同时,移动构造函数标记为noexcept
也向调用者表明此操作不会抛出异常,让调用者可以进行更优化的处理。
- 在类