MST

星途 面试题库

面试题:C++ 智能指针:shared_ptr 的原理及使用场景

请简述 C++ 中 shared_ptr 的工作原理,并举例说明它适用于哪些实际的编程场景。同时,指出使用 shared_ptr 时可能会遇到的问题及解决方案。
19.2万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

shared_ptr工作原理

  1. 引用计数shared_ptr 内部使用引用计数机制来管理所指向的对象。当创建一个 shared_ptr 指向某个对象时,引用计数初始化为1。每次通过拷贝构造函数或赋值运算符创建新的 shared_ptr 指向同一对象时,引用计数加1。当一个 shared_ptr 析构时,引用计数减1。当引用计数降为0时,shared_ptr 会自动释放所指向的对象。
  2. 控制块shared_ptr 通常会维护一个控制块,该控制块不仅包含引用计数,还可能包含其他信息,如删除器(用于自定义对象释放逻辑)和弱引用计数(用于 weak_ptr)。

适用场景

  1. 动态分配对象的共享所有权:例如在一个图形渲染系统中,多个组件可能需要共享一个纹理对象。
#include <memory>
#include <iostream>

class Texture {
public:
    Texture() { std::cout << "Texture created" << std::endl; }
    ~Texture() { std::cout << "Texture destroyed" << std::endl; }
};

void someFunction() {
    std::shared_ptr<Texture> texture1 = std::make_shared<Texture>();
    std::shared_ptr<Texture> texture2 = texture1;
    // 此时texture1和texture2共享同一个Texture对象,引用计数为2
} 
// 函数结束,texture1和texture2析构,引用计数降为0,Texture对象被释放
  1. 容器中存储动态对象:当在 std::vectorstd::list 等容器中存储动态分配的对象时,shared_ptr 可以确保对象在容器销毁或元素移除时正确释放。
#include <memory>
#include <vector>
#include <iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created" << std::endl; }
    ~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};

int main() {
    std::vector<std::shared_ptr<MyClass>> vec;
    vec.emplace_back(std::make_shared<MyClass>());
    // 容器销毁时,MyClass对象会被正确释放
    return 0;
}

可能遇到的问题及解决方案

  1. 循环引用
    • 问题:当两个或多个 shared_ptr 相互引用,形成循环依赖时,会导致引用计数永远不会降为0,从而造成内存泄漏。
    • 示例
#include <memory>
#include <iostream>

class B;

class A {
public:
    std::shared_ptr<B> b;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::shared_ptr<A> a;
    ~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->b = b;
    b->a = a;
    // a和b的引用计数都不会降为0,A和B对象不会被释放
    return 0;
}
  • 解决方案:使用 weak_ptr 打破循环。weak_ptr 不会增加引用计数,它可以观察 shared_ptr 所管理的对象,但不影响对象的生命周期。
#include <memory>
#include <iostream>

class B;

class A {
public:
    std::weak_ptr<B> b;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::weak_ptr<A> a;
    ~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->b = b;
    b->a = a;
    // 离开作用域后,a和b的引用计数降为0,A和B对象被释放
    return 0;
}
  1. 性能开销
    • 问题shared_ptr 的引用计数操作和控制块的维护会带来一定的性能开销,特别是在频繁创建和销毁 shared_ptr 的场景下。
    • 解决方案:在性能敏感的代码段,评估是否可以使用其他更轻量级的内存管理方式,如 unique_ptr(适用于对象只有一个所有者的情况),或者手动管理内存。如果必须使用 shared_ptr,可以尽量减少不必要的 shared_ptr 创建和赋值操作,以降低性能开销。