面试题答案
一键面试std::shared_ptr内存共享机制底层实现细节
- 引用计数的存储方式
std::shared_ptr
通过控制块(control block)来管理引用计数。控制块是一个动态分配的对象,它不仅存储了引用计数,还可能包含弱引用计数(用于std::weak_ptr
)以及指向被管理对象的指针。- 当创建一个
std::shared_ptr
对象时,会同时分配控制块。例如,std::shared_ptr<int> ptr = std::make_shared<int>(42);
,make_shared
会一次性分配被管理对象(这里是int
)和控制块。而使用构造函数std::shared_ptr<int> ptr(new int(42));
,会先分配int
对象,再分配控制块。 - 引用计数存储在控制块中,每次复制
std::shared_ptr
对象,引用计数会递增;每次std::shared_ptr
对象析构,引用计数会递减。当引用计数减为0时,控制块会释放被管理对象的内存,并自身也被释放。
- 处理循环引用问题
- 循环引用是指两个或多个
std::shared_ptr
对象相互引用,导致引用计数永远不会为0,从而造成内存泄漏。例如:
- 循环引用是指两个或多个
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B destroyed" << std::endl; }
};
如果A
和B
之间形成循环引用:
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
}
在上述代码块结束时,a
和b
对象析构,但由于循环引用,A
和B
对象的引用计数不会变为0,导致内存泄漏。
- 解决方法是使用
std::weak_ptr
。std::weak_ptr
不增加引用计数,它可以观察std::shared_ptr
所管理的对象。将上述代码修改为:
class B;
class A {
public:
std::weak_ptr<B> b_ptr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> a_ptr;
~B() { std::cout << "B destroyed" << std::endl; }
};
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
}
这样在代码块结束时,a
和b
对象析构,由于std::weak_ptr
不影响引用计数,A
和B
对象的引用计数变为0,从而正确释放内存。
使用std::shared_ptr时的优化措施
- 使用std::make_shared
std::make_shared
比直接使用std::shared_ptr
构造函数更高效。因为std::make_shared
会一次性分配被管理对象和控制块的内存,而直接构造std::shared_ptr
需要两次分配(一次给对象,一次给控制块)。例如:
// 更高效
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
// 效率较低
std::shared_ptr<int> ptr2(new int(42));
- 减少不必要的引用计数操作
- 避免在频繁调用的函数中进行
std::shared_ptr
的复制操作,因为每次复制都会增加引用计数。可以通过传递std::shared_ptr
的引用或指针来避免不必要的复制。例如:
- 避免在频繁调用的函数中进行
void process(std::shared_ptr<int>& ptr) {
// 处理逻辑
}
- 及时释放不再使用的资源
- 尽早将
std::shared_ptr
置为nullptr
,这样可以提前释放资源,减少不必要的内存占用。例如:
- 尽早将
std::shared_ptr<int> ptr = std::make_shared<int>(42);
// 使用ptr
ptr = nullptr; // 及时释放资源
- 针对多线程环境的优化
- 如果在多线程环境中使用
std::shared_ptr
,要注意引用计数操作的线程安全性。std::shared_ptr
的引用计数操作是原子的,但如果需要对被管理对象进行复杂的多线程操作,可能需要额外的同步机制。可以考虑使用std::shared_mutex
等同步工具来保护对被管理对象的访问,以提高性能。同时,尽量减少在多线程间频繁传递std::shared_ptr
,以降低锁竞争。
- 如果在多线程环境中使用