C++--shared_ptr

std::shared_ptr 是 C++ 标准库 <memory> 头文件中提供的一种智能指针,用于管理动态分配的对象。它采用引用计数的机制来跟踪有多少个 std::shared_ptr 共享同一个对象。每当一个新的 std::shared_ptr 指向该对象时,引用计数加 1;当一个 std::shared_ptr 被销毁或者指向其他对象时,引用计数减 1。当引用计数变为 0 时,对象的内存会被自动释放。

作用

  • 自动内存管理 :和 std::unique_ptr 类似,std::shared_ptr 可以在对象不再被使用时自动释放其内存,避免手动调用 delete 带来的内存泄漏风险。
  • 共享所有权 :多个 std::shared_ptr 可以同时指向同一个对象,它们共享该对象的所有权。这使得在多个地方使用同一个对象时更加方便,无需担心对象的生命周期管理问题。

场景

  • 多个对象共享资源 :当多个不同的部分需要访问和使用同一个动态分配的对象时,使用 std::shared_ptr 可以确保资源的正确管理。例如,在一个图形应用程序中,多个图形元素可能共享同一个纹理资源,使用 std::shared_ptr 来管理这个纹理对象,当所有图形元素都不再使用该纹理时,纹理对象的内存会被自动释放。
  • 在容器中存储可共享的对象 :可以将 std::shared_ptr 存储在标准容器(如 std::vectorstd::list 等)中,方便对多个共享对象进行管理。
  • 实现对象之间的复杂引用关系 :在一些复杂的程序中,对象之间可能存在相互引用的情况。使用 std::shared_ptr 可以更好地处理这些引用关系,避免悬空指针和内存泄漏。

用法举例

1. 基本使用

c 复制代码
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
    void doSomething() { std::cout << "Doing something..." << std::endl; }
};

int main() {
    // 创建一个 std::shared_ptr 并指向一个新的 MyClass 对象
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    // 创建另一个 std::shared_ptr 并共享同一个对象
    std::shared_ptr<MyClass> ptr2 = ptr1;

    std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;  // 输出引用计数
    ptr2->doSomething();

    // 当 ptr1 和 ptr2 都离开作用域时,引用计数变为 0,对象被销毁
    return 0;
}

在这个例子中,std::make_shared<MyClass>() 用于创建一个 MyClass 对象,并返回一个 std::shared_ptr 指向该对象。然后将 ptr1 赋值给 ptr2,此时它们共享同一个 MyClass 对象,引用计数变为 2。use_count() 方法可以返回当前对象的引用计数。当 ptr1ptr2 都离开 main 函数的作用域时,引用计数减为 0,对象的内存被自动释放。

2. 在容器中使用

c 复制代码
#include <iostream>
#include <memory>
#include <vector>

class MyClass {
public:
    MyClass(int value) : data(value) { std::cout << "MyClass constructor with value: " << data << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor with value: " << data << std::endl; }
    int getData() const { return data; }
private:
    int data;
};

int main() {
    std::vector<std::shared_ptr<MyClass>> vec;
    // 向容器中添加元素
    vec.push_back(std::make_shared<MyClass>(1));
    vec.push_back(std::make_shared<MyClass>(2));

    // 访问容器中的元素
    for (const auto& ptr : vec) {
        std::cout << "Data: " << ptr->getData() << std::endl;
    }

    // 容器销毁时,所有 shared_ptr 的引用计数减 1
    // 当没有其他 shared_ptr 指向这些对象时,对象的内存会被释放
    return 0;
}

在这个例子中,std::vector 存储了多个 std::shared_ptr<MyClass> 对象。向容器中添加元素时,使用 std::make_shared 创建新的 MyClass 对象并将其所有权交给 std::shared_ptr,然后添加到容器中。当容器销毁时,所有 std::shared_ptr 的引用计数减 1,当引用计数变为 0 时,对象的内存会被自动释放。

3. 作为函数参数和返回值

c 复制代码
#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }
    void doSomething() { std::cout << "Doing something..." << std::endl; }
};

// 接受一个 std::shared_ptr 作为参数
void processObject(std::shared_ptr<MyClass> obj) {
    obj->doSomething();
    std::cout << "Inside processObject, use count: " << obj.use_count() << std::endl;
}

// 返回一个 std::shared_ptr
std::shared_ptr<MyClass> createObject() {
    return std::make_shared<MyClass>();
}

int main() {
    std::shared_ptr<MyClass> obj = createObject();
    std::cout << "Before calling processObject, use count: " << obj.use_count() << std::endl;
    processObject(obj);
    std::cout << "After calling processObject, use count: " << obj.use_count() << std::endl;
    return 0;
}

在这个例子中,createObject 函数返回一个 std::shared_ptr,表示创建并返回一个新的 MyClass 对象的共享所有权。processObject 函数接受一个 std::shared_ptr 作为参数,当调用 processObject(obj) 时,obj 的引用计数加 1。在 processObject 函数内部,引用计数为 2;函数返回后,引用计数减 1 变回 1。当 obj 离开 main 函数的作用域时,引用计数变为 0,对象的内存被自动释放。

注意事项:

1. 循环引用问题

  • 问题描述 :当两个或多个对象通过 std::shared_ptr 相互引用时,会形成循环引用,导致引用计数永远不会降为 0,从而造成内存泄漏。
  • 示例代码
c 复制代码
#include <iostream>
#include <memory>

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; }
};

int main() {
    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;
    return 0;
}
  • 解决方案 :使用 std::weak_ptr 来打破循环引用。std::weak_ptr 是一种不增加引用计数的智能指针,它可以观测 std::shared_ptr 所管理的对象。修改上述代码如下:
c 复制代码
#include <iostream>
#include <memory>

class B;

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

class B {
public:
    std::weak_ptr<A> a_ptr; // 使用 std::weak_ptr 打破循环引用
    ~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_ptr = b;
    b->a_ptr = a;
    return 0;
}

2. 避免手动管理和 std::shared_ptr 混用

  • 问题描述 :如果手动管理对象的内存(使用 newdelete),同时又使用 std::shared_ptr 管理同一个对象,可能会导致对象被多次删除,从而引发未定义行为。
  • 示例代码
c 复制代码
#include <memory>

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

int main() {
    MyClass* rawPtr = new MyClass();
    std::shared_ptr<MyClass> sharedPtr(rawPtr);
    delete rawPtr; // 错误:手动删除已经被 std::shared_ptr 管理的对象
    return 0;
}
  • 解决方案 :始终使用 std::make_shared 来创建 std::shared_ptr,避免手动使用 new。例如:
c 复制代码
#include <memory>

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

int main() {
    std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
    return 0;
}

3. 线程安全问题

  • 问题描述std::shared_ptr 的引用计数操作是线程安全的,但对其所管理对象的访问不是线程安全的。如果多个线程同时访问和修改 std::shared_ptr 所管理的对象,可能会导致数据竞争和未定义行为。
  • 示例代码
c 复制代码
#include <iostream>
#include <memory>
#include <thread>

class MyClass {
public:
    int value = 0;
    void increment() { ++value; }
};

void worker(std::shared_ptr<MyClass> obj) {
    for (int i = 0; i < 100000; ++i) {
        obj->increment();
    }
}

int main() {
    std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
    std::thread t1(worker, obj);
    std::thread t2(worker, obj);
    t1.join();
    t2.join();
    std::cout << "Final value: " << obj->value << std::endl;
    return 0;
}
  • 解决方案 :在多线程环境中,对 std::shared_ptr 所管理的对象的访问需要进行同步。可以使用互斥锁(std::mutex)来保护对象的访问。修改上述代码如下:
c 复制代码
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>

class MyClass {
public:
    int value = 0;
    std::mutex mtx;
    void increment() {
        std::lock_guard<std::mutex> lock(mtx);
        ++value;
    }
};

void worker(std::shared_ptr<MyClass> obj) {
    for (int i = 0; i < 100000; ++i) {
        obj->increment();
    }
}

int main() {
    std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
    std::thread t1(worker, obj);
    std::thread t2(worker, obj);
    t1.join();
    t2.join();
    std::cout << "Final value: " << obj->value << std::endl;
    return 0;
}

4. 性能开销

  • 问题描述std::shared_ptr 的引用计数操作会带来一定的性能开销,尤其是在频繁创建和销毁 std::shared_ptr 的场景下。
  • 解决方案 :如果不需要共享所有权,尽量使用 std::unique_ptr,它的性能开销相对较小。只有在确实需要共享对象所有权时,才使用 std::shared_ptr

5. 异常安全问题

  • 问题描述 :在创建 std::shared_ptr 时,如果发生异常,可能会导致部分资源分配成功而部分失败,从而造成资源泄漏。
  • 解决方案 :使用 std::make_shared 来创建 std::shared_ptr,它可以保证在异常发生时不会有资源泄漏。因为 std::make_shared 会一次性分配对象和引用计数的内存,要么全部成功,要么全部失败。
相关推荐
Lzc77429 分钟前
C++进阶——哈希表的实现
c++·哈希表的实现
三体世界31 分钟前
C++ STL 序列式容器之(三)-- List
java·c语言·开发语言·c++·windows·list·visual studio
愚戏师40 分钟前
C++:向Lambda传递参数与捕获
开发语言·c++
努力学习的小廉44 分钟前
【C++】 —— 笔试刷题day_8
开发语言·c++
你好,明天,,1 小时前
OpenCV三维解算常用方法C++
c++·数码相机·opencv
背影疾风1 小时前
C++学习之路:从头搞懂配置VScode开发环境的逻辑与步骤
c++·vscode·学习
旧厂街小江1 小时前
LeetCode第93题:复原IP地址
c++·python·算法
小冯的编程学习之路2 小时前
【C++项目实战】:基于正倒排索引的Boost搜索引擎(1)
开发语言·c++·搜索引擎
ScilogyHunter2 小时前
探索Google Test(gtest):C++单元测试的强大工具
c++·单元测试·gtest
大锦终2 小时前
详解vector容器
c语言·开发语言·数据结构·c++