面试题:请介绍 C++ 中 shared_ptr 的原理?shared_ptr 线程安全吗?

std::shared_ptr 是 C++ 标准库中的一种智能指针,用于管理动态分配的对象生命周期。它通过引用计数机制实现多个 std::shared_ptr 共享对象的所有权。理解 std::shared_ptr 的原理需要从它的内部实现、引用计数机制以及线程安全性入手。


std::shared_ptr 的原理

1. 引用计数机制

std::shared_ptr 的核心是引用计数机制。每个由 std::shared_ptr 管理的对象都会关联一个控制块(control block),控制块中存储以下信息:

  • 强引用计数(use count):记录当前有多少个 std::shared_ptr 共享对象的所有权。
  • 弱引用计数(weak count):记录当前有多少个 std::weak_ptr 正在观察对象。
  • 指向对象的指针:指向实际管理的对象。
  • 删除器(deleter):用于自定义对象的销毁方式(可选)。

当一个新的 std::shared_ptr 指向同一个对象时,强引用计数会增加;当一个 std::shared_ptr 被销毁或重置时,强引用计数会减少。当强引用计数归零时,对象会被销毁,但控制块不会立即释放,直到弱引用计数也归零。

2. 控制块的创建

控制块的创建方式有两种:

  • 通过 std::make_shared 创建
    • std::make_shared 会一次性分配内存,既存储对象又存储控制块。
    • 这种方式更高效,因为减少了内存分配次数。
  • 通过 std::shared_ptr 构造函数创建
    • 如果直接使用 new 创建对象并传递给 std::shared_ptr,则会单独分配控制块。
    • 这种方式会导致两次内存分配(对象和控制块)。

3. 共享所有权

多个 std::shared_ptr 可以共享同一个对象的所有权。每个 std::shared_ptr 都指向相同的控制块,并通过控制块管理对象的生命周期。


std::shared_ptr 的线程安全性

1. 引用计数的线程安全性

  • std::shared_ptr 的引用计数操作是原子的,因此多个线程同时拷贝或销毁 std::shared_ptr 是线程安全的。

2. 对象访问的线程安全性

  • std::shared_ptr 只保证引用计数的线程安全性,不保证对象本身的线程安全性。
  • 如果多个线程同时访问同一个对象,需要额外的同步机制(如互斥锁)来保证线程安全。

3. 控制块的线程安全性

  • 控制块的操作(如引用计数的增减)是线程安全的,但控制块中的其他数据(如删除器)可能不是线程安全的。

4. 注意事项

  • 如果多个线程同时修改同一个 std::shared_ptr 对象(例如赋值或重置),可能会导致竞争条件。此时需要使用原子操作或互斥锁来保护。

使用示例

#include <iostream>
#include <memory>
#include <thread>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructed!" << std::endl; }
    ~MyClass() { std::cout << "MyClass destroyed!" << std::endl; }
    void doSomething() { std::cout << "Doing something!" << std::endl; }
};

void thread_func(std::shared_ptr<MyClass> ptr) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    ptr->doSomething();
}

int main() {
    std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();

    std::thread t1(thread_func, ptr);
    std::thread t2(thread_func, ptr);

    t1.join();
    t2.join();

    return 0;
}

输出:

MyClass constructed!
Doing something!
Doing something!
MyClass destroyed!

总结

  • 原理
    • std::shared_ptr 通过引用计数机制管理对象的生命周期。
    • 控制块存储引用计数和对象指针,多个 std::shared_ptr 共享同一个控制块。
  • 线程安全性
    • 引用计数操作是线程安全的。
    • 对象访问和控制块的其他操作需要额外的同步机制来保证线程安全。

这个问题考察的是对 std::shared_ptr 的实现原理和线程安全性的理解,是 C++ 面试中的常见题目。

THE END
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容