C++ 智能指针 shared_ptr

std::shared_ptr 是 C++11 引入的智能指针类型之一,它管理动态分配的对象,并自动在不再需要时释放该对象,避免了手动管理内存的麻烦。下面将对 shared_ptr 进行详细讲解,包括其初始化、基本使用、应用场景、demo 以及注意事项。

1. std::shared_ptr 简介

shared_ptr 是一种引用计数智能指针,多个 shared_ptr 对象可以共享同一个资源。当最后一个 shared_ptr 不再引用该资源时,资源会被自动释放。适用于需要多个对象共享同一资源的场景。

2. 初始化与基本使用

初始化

可以通过 std::make_shared 或直接构造 shared_ptr 对象来进行初始化:

#include <iostream>
#include <memory>

int main() {
    // 使用 make_shared 初始化
    std::shared_ptr<int> sp1 = std::make_shared<int>(100);

    // 直接构造 shared_ptr
    std::shared_ptr<int> sp2(new int(200));

    std::cout << "sp1: " << *sp1 << std::endl;
    std::cout << "sp2: " << *sp2 << std::endl;

    return 0;
}

std::make_shared 通常是更推荐的做法,因为它更高效且能避免内存泄漏。

基本使用
  • 获取引用计数:使用 use_count() 获取当前资源的引用计数。
  • 访问资源:使用 * 解引用,或者使用 -> 访问资源的成员。
  • 释放资源:当所有的 shared_ptr 都不再持有该资源时,资源会被自动释放。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp1 = std::make_shared<int>(42);
    std::shared_ptr<int> sp2 = sp1; // 引用计数加1

    std::cout << "sp1: " << *sp1 << ", 引用计数: " << sp1.use_count() << std::endl;
    std::cout << "sp2: " << *sp2 << ", 引用计数: " << sp2.use_count() << std::endl;

    sp2.reset(); // sp2 释放,引用计数减1

    std::cout << "sp1: " << *sp1 << ", 引用计数: " << sp1.use_count() << std::endl;

    return 0;
}

3. 应用场景

  • 资源共享:当多个对象需要共享一个动态分配的资源时,例如多个模块共享同一个配置文件、数据结构等。
  • 避免内存泄漏:通过自动管理内存释放,避免手动调用 delete 带来的内存泄漏问题。
  • 多线程场景:可以安全地跨多个线程共享资源,shared_ptr 本身是线程安全的。

4. 示例 Demo

下面是一个简单的 demo,展示了如何在多个函数中共享同一资源:

#include <iostream>
#include <memory>

void foo(std::shared_ptr<int> sp) {
    std::cout << "foo 中的值: " << *sp << ", 引用计数: " << sp.use_count() << std::endl;
}

void bar(std::shared_ptr<int> sp) {
    std::cout << "bar 中的值: " << *sp << ", 引用计数: " << sp.use_count() << std::endl;
}

int main() {
    std::shared_ptr<int> sp = std::make_shared<int>(100);
    std::cout << "main 中的引用计数: " << sp.use_count() << std::endl;

    foo(sp);
    bar(sp);

    std::cout << "main 中的引用计数: " << sp.use_count() << std::endl;
    return 0;
}

输出:
main 中的引用计数: 1
foo 中的值: 100, 引用计数: 2
bar 中的值: 100, 引用计数: 2
main 中的引用计数: 1

5. 注意事项与常见坑

1. 循环引用问题

shared_ptr 的一个常见陷阱是循环引用,当两个或多个对象互相持有对方的 shared_ptr 时,引用计数永远不会为 0,导致资源永远不会释放,造成内存泄漏。解决方案是使用 std::weak_ptr 打破循环引用。

#include <iostream>
#include <memory>

struct Node {
    std::shared_ptr<Node> next;
    ~Node() { std::cout << "Node 析构" << std::endl; }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();

    // 循环引用
    node1->next = node2;
    node2->next = node1;

    // 资源不会被释放,造成内存泄漏
    return 0;
}
使用 weak_ptr 可以解决这个问题:

struct Node {
    std::weak_ptr<Node> next;
    ~Node() { std::cout << "Node 析构" << std::endl; }
};

2. 性能开销

shared_ptr 使用了引用计数机制,每次拷贝都会导致引用计数的增加和减少,频繁使用时可能带来一定的性能开销。在性能敏感的场合,可以考虑使用 std::unique_ptr,它没有引用计数的开销。

3. 避免混合使用裸指针

不要混合使用 shared_ptr 和裸指针,特别是在传递和管理资源时。因为 shared_ptr 是自动管理内存的,如果传入一个裸指针,很容易造成重复释放资源或内存泄漏。

6. 使用细节

1. std::move 与所有权转移

std::shared_ptr 支持通过 std::move 将所有权从一个 shared_ptr 对象转移到另一个对象中,类似于其他智能指针。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp1 = std::make_shared<int>(100);
    std::shared_ptr<int> sp2 = std::move(sp1);  // sp1 所有权转移给 sp2

    if (!sp1) {
        std::cout << "sp1 为空" << std::endl;
    }
    std::cout << "sp2 的值: " << *sp2 << std::endl;

    return 0;
}

通过 std::move,可以将 shared_ptr 的所有权从一个对象转移到另一个对象,而不会增加引用计数。此操作特别适合于需要临时拥有对象的函数或容器。

unique_ptr 转换为 shared_ptr

std::unique_ptr 不能直接复制或共享资源,但可以将其转换为 shared_ptr,使其所有权转移给 shared_ptr。这种转换主要适用于那些资源最初只被单独拥有,后来需要共享的场景。

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> up = std::make_unique<int>(100);
    std::shared_ptr<int> sp = std::move(up);  // 将 unique_ptr 转换为 shared_ptr

    std::cout << "sp 的值: " << *sp << std::endl;

    return 0;
}
 

2.reset() 改变与资源的关联

shared_ptr::reset() 用于解除当前 shared_ptr 与其管理资源的关联。调用 reset() 后,原资源的引用计数减1,当引用计数为0时,资源会被释放。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp = std::make_shared<int>(100);
    std::cout << "sp 的引用计数: " << sp.use_count() << std::endl;

    sp.reset();  // 解除与资源的关系
    std::cout << "sp 解除关联后: " << (sp ? "有资源" : "没有资源") << std::endl;

    sp.reset(new int(200));  // 关联新的资源
    std::cout << "新资源的值: " << *sp << std::endl;

    return 0;
}

3.swap() 交换两个 shared_ptr

shared_ptr::swap() 函数用于交换两个 shared_ptr 对象管理的资源。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp1 = std::make_shared<int>(100);
    std::shared_ptr<int> sp2 = std::make_shared<int>(200);

    std::cout << "交换前 sp1 的值: " << *sp1 << ", sp2 的值: " << *sp2 << std::endl;

    sp1.swap(sp2);  // 交换 sp1 和 sp2 管理的资源

    std::cout << "交换后 sp1 的值: " << *sp1 << ", sp2 的值: " << *sp2 << std::endl;

    return 0;
}
 

4. 多态支持

shared_ptr 同样支持多态行为。当 shared_ptr 管理基类指针时,若该指针指向派生类对象,可以通过基类指针调用派生类对象的虚函数。

#include <iostream>
#include <memory>

class Base {
public:
    virtual void show() { std::cout << "Base 类" << std::endl; }
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void show() override { std::cout << "Derived 类" << std::endl; }
};

int main() {
    std::shared_ptr<Base> basePtr = std::make_shared<Derived>();  // shared_ptr 指向派生类对象
    basePtr->show();  // 调用派生类的函数

    return 0;
}

5.exit() 调用时的资源释放

调用 exit() 会立即终止程序的执行,这会导致局部 shared_ptr 无法正常释放其管理的资源。需要注意避免程序在存在未释放的局部资源的情况下意外调用 exit()。全局或静态 shared_ptr 会在程序退出时被自动释放。

6. 数组支持

shared_ptr 还支持管理动态数组,通过 shared_ptr 管理数组时,可以使用 operator[] 来访问数组元素。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int[]> arr(new int[5]{1, 2, 3, 4, 5});

    arr[2] = 10;  // 修改数组中的元素
    for (int i = 0; i < 5; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

7. 线程安全性

std::shared_ptr 的引用计数操作是线程安全的,因为引用计数是原子操作。在多个线程中只进行读取操作时是线程安全的,但如果多个线程同时对同一个 shared_ptr 对象进行读写,或者同时访问 shared_ptr 指向的资源,则需要加锁保护。

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

void thread_func(std::shared_ptr<int> sp) {
    std::cout << "线程中共享的值: " << *sp << std::endl;
}

int main() {
    auto sp = std::make_shared<int>(42);
    std::thread t1(thread_func, sp);
    std::thread t2(thread_func, sp);

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

    return 0;
}

8.使用 unique_ptr 替代 shared_ptr

如果一个资源只需要由一个对象管理,没有共享的需求,推荐使用 std::unique_ptr,因为它没有引用计数的开销,效率更高,占用的内存资源也更少。

std::unique_ptr 不能复制,但可以转移所有权,适合用于局部资源管理和简单场景。如果在多个模块中共享资源时,可以考虑使用 shared_ptr

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> up = std::make_unique<int>(100);
    std::cout << "unique_ptr 的值: " << *up << std::endl;

    // std::unique_ptr<int> up2 = up;  // 错误,不能复制 unique_ptr
    std::unique_ptr<int> up2 = std::move(up);  // 转移所有权
    std::cout << "转移后 up2 的值: " << *up2 << std::endl;

    return 0;
}

总结

std::shared_ptr 提供了强大的内存管理功能,特别适合资源共享的场景。它有许多高级特性,如支持 std::movereset()swap()、多态行为、线程安全等。在使用时需要注意避免循环引用,并根据场景选择更高效的 unique_ptr

<think>好的,我现在需要回答用户关于C++shared_ptr的使用方法及示例的问题。首先,我应该回忆一下自己对shared_ptr的了解,并结合提供的引用资料来确保信息的准确性。 首先,shared_ptrC++智能指针的一种,用于自动管理动态分配的内存,防止内存泄漏。根据引用1,shared_ptr采用引用计数机制,多个shared_ptr可以共享同一个对象,当最后一个shared_ptr被销毁时,对象才会被释放。引用2提到shared_ptr允许拷贝和赋值,这说明它们支持共享所有权。引用3则补充了shared_ptr销毁对象的条件,即最后一个拥有对象的shared_ptr被销毁或重置时。 接下来,我需要组织回答的结构。用户需要使用方法及示例,所以应该分步骤讲解,并附上代码示例。可能的要点包括:创建shared_ptr、共享所有权、自定义删除器、注意事项等。 创建shared_ptr部分,需要说明如何用make_shared或直接构造。共享所有权要展示多个指针指向同一对象的情况。自定义删除器可以举文件关闭的例子。注意事项要提到循环引用的问题,并引入weak_ptr作为解决方案。 在写代码示例时,要确保语法正确,并且每段代码有注释解释。同时,要引用提供的资料中的信息,比如在说明引用计数时引用[^1],在提到拷贝赋值时引用[^2],在销毁条件时引用[^3]。 另外,用户的问题可能涉及相关技术问题,比如循环引用,所以需要解释问题原因和解决方法。这里需要提到weak_ptr,并给出示例,如引用3中提到的weak_ptr用于打破循环。 最后,生成相关问题时要围绕shared_ptr的扩展内容,比如与unique_ptr的区别、循环引用的解决、性能分析等。 需要检查是否符合用户的要求:使用中文、正确格式的数学公式(如果有的话)、引用标识、代码块格式正确。但在这个问题中可能没有数学公式,但如果有涉及计算的地方,需要按格式处理。 总结回答的结构: 1. shared_ptr基本概念和引用计数机制 2. 创建方法(make_shared) 3. 共享所有权示例 4. 自定义删除器 5. 注意事项(循环引用和weak_ptr) 6. 示例代码 7. 相关问题 确保每个部分都简洁明了,引用正确的资料,示例代码正确无误。</think>### C++ shared_ptr 使用方法及示例 #### 1. shared_ptr 基本概念 `shared_ptr` 是 C++ 标准库提供的智能指针,通过**引用计数机制**管理动态分配的内存。多个 `shared_ptr` 可以指向同一对象,当最后一个 `shared_ptr` 被销毁或重置时,对象内存会被自动释放[^1]。 --- #### 2. 使用方法 ##### (1) 创建 shared_ptr 推荐使用 `std::make_shared` 创建,既安全又高效: ```cpp #include <memory> // 创建指向 int 的 shared_ptr auto sp1 = std::make_shared<int>(42); // 直接构造(不推荐,可能引发异常安全问题) std::shared_ptr<int> sp2(new int(100)); ``` ##### (2) 共享所有权 多个 `shared_ptr` 可以共享同一对象,引用计数自动增加/减少: ```cpp auto sp3 = sp1; // 引用计数变为2 sp3.reset(); // 引用计数恢复为1 ``` ##### (3) 自定义删除器 默认删除器调用 `delete`,但可自定义(如用于文件句柄): ```cpp std::shared_ptr<FILE> file_ptr( fopen("test.txt", "r"), [](FILE* f) { if (f) fclose(f); } ); ``` --- #### 3. 注意事项 ##### 循环引用问题 若两个 `shared_ptr` 互相引用,会导致内存泄漏。需用 `weak_ptr` 打破循环: ```cpp class B; // 前向声明 class A { public: std::shared_ptr<B> b_ptr; }; class B { public: std::weak_ptr<A> a_weak_ptr; // 使用 weak_ptr 避免循环引用 }; ``` --- #### 4. 完整示例 ```cpp #include <iostream> #include <memory> struct Data { Data() { std::cout << "Data created\n"; } ~Data() { std::cout << "Data destroyed\n"; } }; int main() { // 创建 shared_ptr auto d1 = std::make_shared<Data>(); { auto d2 = d1; // 引用计数变为2 std::cout << "d2 使用中,引用计数=" << d2.use_count() << "\n"; } // d2 销毁,引用计数恢复为1 std::cout << "d1 引用计数=" << d1.use_count() << "\n"; return 0; } ``` **输出**: ``` Data created d2 使用中,引用计数=2 d1 引用计数=1 Data destroyed ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值