C++: shared_ptr是线程安全的吗

导读

C++面试中有时会有这样一个问题,shared_ptr是线程安全的吗?对此问题,我们需要从三个并发场景进行考虑,拷贝shared_ptr的安全性、对shared_ptr赋值的安全性和读写shared_ptr指向内存区域的安全性。

对于以上问题,首先给出以下结论:

  • 如果多个线程同时拷贝同一个shared_ptr对象,不会有问题,因为shared_ptr的引用计数是线程安全的。
  • 如果多个线程同时修改同一个shared_ptr 对象,不是线程安全的。
  • 如果多个线程同时读写shared_ptr指向的内存对象,不是线程安全的。

下面通过简单程序实验证明:

1. 引用计数更新,线程安全

这里我们讨论对shared_ptr进行拷贝的情况,由于此操作读写的是引用计数,而引用计数的更新是原子操作,因此这种情况是线程安全的。下面这个例子,两个线程同时对同一个shared_ptr进行拷贝,引用计数的值总是20001。

std::shared_ptr<int> p = std::make_shared<int>(0);
constexpr int N = 10000;
std::vector<std::shared_ptr<int>> sp_arr1(N);
std::vector<std::shared_ptr<int>> sp_arr2(N);

void increment_count(std::vector<std::shared_ptr<int>>& sp_arr) {
    for (int i = 0; i < N; i++) {
        sp_arr[i] = p;
    }
}

std::thread t1(increment_count, std::ref(sp_arr1));
std::thread t2(incre
### C++ 中 `std::unique_ptr` 和 `std::shared_ptr` 的作用及用法 #### 1. `std::unique_ptr` `std::unique_ptr` 是一种独占所有权的智能指针,它确保同一时间只有一个 `std::unique_ptr` 实例拥有某个对象的所有权[^2]。这意味着一旦另一个 `std::unique_ptr` 接管该对象的所有权,原来的 `std::unique_ptr` 将不再持有任何资源。 ##### 特点 - **唯一性**:`std::unique_ptr` 不允许复制操作,仅可以通过移动语义来转移所有权。 - **自动释放内存**:当 `std::unique_ptr` 超出其作用域时,其所管理的对象会被自动销毁并释放内存。 - **轻量级**:由于不涉及引用计数机制,`std::unique_ptr` 的开销较小,通常只有指针本身的大小[^2]。 ##### 使用方法 下面是一个简单的例子展示如何创建和使用 `std::unique_ptr`: ```cpp #include <iostream> #include <memory> int main() { // 创建 unique_ptr 并初始化为 new int(10) std::unique_ptr<int> uptr = std::make_unique<int>(10); // 访问 managed 对象的内容 std::cout << *uptr << std::endl; // 移动 ownership 到另一个 unique_ptr std::unique_ptr<int> uptr2 = std::move(uptr); // 此时 uptr 已经为空,访问会导致未定义行为 if (!uptr) { std::cout << "uptr is empty." << std::endl; } return 0; } ``` --- #### 2. `std::shared_ptr` `std::shared_ptr` 是一种共享所有权的智能指针,允许多个 `std::shared_ptr` 同时管理同一个对象[^3]。通过内部维护的一个引用计数器,`std::shared_ptr` 可以跟踪有多少个实例正在共同管理这个对象。当最后一个 `std::shared_ptr` 被销毁或者放弃对该对象的所有权时,才会真正释放该对象的内存。 ##### 特点 - **共享所有权**:多个 `std::shared_ptr` 可以同时指向同一个动态分配的对象。 - **引用计数**:每当一个新的 `std::shared_ptr` 复制已有的 `std::shared_ptr` 或者被赋值给其他 `std::shared_ptr` 时,引用计数加一;反之减一。 - **线程安全**:引用计数的操作是原子性的,在多线程环境下可以安全地增减引用计数。 ##### 使用方法 以下代码展示了如何创建和使用 `std::shared_ptr`: ```cpp #include <iostream> #include <memory> class MyClass { public: MyClass(int value) : m_value(value) {} void display() const { std::cout << "Value: " << m_value << std::endl; } private: int m_value; }; int main() { // 创建 shared_ptr 并初始化为 new MyClass(42) std::shared_ptr<MyClass> sptr1 = std::make_shared<MyClass>(42); // 输出当前引用计数值 std::cout << "Use count after creating sptr1: " << sptr1.use_count() << std::endl; // 创建第二个 shared_ptr 拷贝第一个 std::shared_ptr<MyClass> sptr2 = sptr1; // 输出新的引用计数值 std::cout << "Use count after copying to sptr2: " << sptr1.use_count() << std::endl; // 调用成员函数 sptr2->display(); return 0; } ``` --- #### 主要区别 | 属性 | `std::unique_ptr` | `std::shared_ptr` | |---------------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| | 所有权 | 唯一所有权 | 共享所有权 | | 引用计数 | 无引用计数(简单高效) | 维护一个引用计数器 | | 性能 | 更高效率,因为不需要额外的空间存储引用计数 | 较低性能,因需维护引用计数 | | 线程安全性 | 非线程安全 | 引用计数部分是线程安全的 | --- 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值