面试题答案
一键面试智能指针原理
std::unique_ptr
:- 原理:
std::unique_ptr
采用独占式所有权模型。它拥有指向对象的唯一指针,当std::unique_ptr
被销毁时(例如离开其作用域),它所指向的对象也会被自动销毁。这是通过delete
操作符来实现的,std::unique_ptr
的析构函数会调用delete
来释放其管理的资源。 - 特点:
- 独占所有权,同一时间只有一个
std::unique_ptr
可以指向特定对象。 - 不能进行拷贝构造和拷贝赋值操作,但可以进行移动构造和移动赋值操作,这使得它在性能上较为高效,因为移动操作通常是廉价的。
- 独占所有权,同一时间只有一个
- 适用场景:适用于资源管理中,某个对象只应由一个所有者负责的情况,例如在函数内部创建一个对象并返回,
std::unique_ptr
可以安全地管理该对象的生命周期,而无需手动delete
。
- 原理:
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::weak_ptr
:- 原理:
std::weak_ptr
是为了配合std::shared_ptr
解决循环引用问题而引入的。它指向由std::shared_ptr
管理的对象,但并不增加对象的引用计数。std::weak_ptr
可以从一个std::shared_ptr
创建,通过lock()
方法可以尝试获取一个有效的std::shared_ptr
,如果对象已被释放(引用计数为 0),lock()
方法会返回一个空的std::shared_ptr
。 - 特点:
- 不拥有对象的所有权,不会影响对象的引用计数。
- 主要用于打破
std::shared_ptr
之间的循环引用,避免内存泄漏。 - 可以用来检查
std::shared_ptr
所管理的对象是否仍然存在。
- 适用场景:当存在可能的循环引用时,使用
std::weak_ptr
来打破循环。例如在双向链表中,节点之间可能存在相互引用,使用std::weak_ptr
可以避免循环引用导致的内存泄漏。
- 原理:
在复杂类继承体系和资源管理场景下智能指针的选择与使用
- 选择智能指针:
- 如果一个对象只有一个所有者,并且不需要在不同地方共享,优先使用
std::unique_ptr
,这样可以获得更好的性能和更清晰的所有权关系。 - 当需要在多个地方共享资源,且资源的生命周期需要由多个使用者共同决定时,使用
std::shared_ptr
。但要注意循环引用问题,如有可能出现循环引用,配合std::weak_ptr
使用。 - 在可能存在循环引用的类继承体系中,例如基类和派生类之间存在相互引用,或者不同派生类之间存在相互引用时,合理使用
std::weak_ptr
来打破循环。
- 如果一个对象只有一个所有者,并且不需要在不同地方共享,优先使用
- 示例代码:
#include <iostream>
#include <memory>
class Base {
public:
virtual ~Base() {
std::cout << "Base destroyed" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destroyed" << std::endl;
}
};
// 演示 std::unique_ptr 的使用
std::unique_ptr<Base> createUnique() {
return std::make_unique<Derived>();
}
// 演示 std::shared_ptr 和 std::weak_ptr 的使用
class Parent;
class Child {
public:
std::weak_ptr<Parent> parent;
~Child() {
std::cout << "Child destroyed" << std::endl;
}
};
class Parent {
public:
std::shared_ptr<Child> child;
~Parent() {
std::cout << "Parent destroyed" << std::endl;
}
};
int main() {
// 使用 std::unique_ptr
{
auto up = createUnique();
// up 离开作用域时,Derived 对象会自动销毁
}
// 使用 std::shared_ptr 和 std::weak_ptr
{
auto p = std::make_shared<Parent>();
auto c = std::make_shared<Child>();
p->child = c;
c->parent = p;
// 检查 child 指向的对象是否存在
auto locked = c->parent.lock();
if (locked) {
std::cout << "Parent still exists" << std::endl;
} else {
std::cout << "Parent has been destroyed" << std::endl;
}
}
// p 和 c 离开作用域时,Parent 和 Child 对象会自动销毁,由于使用了 std::weak_ptr 打破循环,不会造成内存泄漏
return 0;
}
在上述代码中:
createUnique
函数演示了std::unique_ptr
的使用,函数返回一个std::unique_ptr<Base>
,指向一个Derived
对象,当up
离开作用域时,Derived
对象会自动销毁。Parent
和Child
类之间存在潜在的循环引用,通过在Child
类中使用std::weak_ptr
指向Parent
来打破循环。当p
和c
离开作用域时,Parent
和Child
对象都会被正确销毁,避免了内存泄漏。std::weak_ptr
的lock
方法可以检查所指向的对象是否仍然存在。