【C++智能指针深度解析】std::shared_ptr、std::unique_ptr与std::weak_ptr的构造、原理及应用实战

智能指针是C++11引入的新特性,因为C++使用内存的时候很容易出现内存泄漏、野指针、悬空指针的问题,所以C++11引入了智能指针来管理内存

这篇文章会详细介绍了三种智能指针:std::unique_ptrstd::shared_ptrstd::weak_ptr的原理、使用方法和适用场景。

目录

std::shared_ptr

构造函数

std::shared_ptr的底层原理 

模拟实现简单shared_ptr类

什么时候用shared_ptr

std::unique_ptr类

模拟实现unique_ptr类

什么时候用unique_ptr 

std::weak_ptr类

使用weak_ptr解决循环引用


std::shared_ptr

共享式指针,同一时刻可以有多个指针指向一个对象

shared_ptr对象一旦被销毁,或者其值通过赋值操作或显式调用shared_ptr::reset而发生变化,就会立即释放它们共同拥有的对象的所有权。

 shared_ptr指针的对象有获取指针所有权以及共享该指针所有权的能力,一旦它们获取了所有权,当最后一个所有者释放它时,指针的所有者组负责释放掉它。

我们先来看一下官方的构造函数 

构造函数

 能看超过三分钟都是神人了,我们还是来看几个实例吧!

#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
  std::shared_ptr<int> p1;
  std::shared_ptr<int> p2 (nullptr);
  std::shared_ptr<int> p3 (new int);
  std::shared_ptr<int> p4 (new int, std::default_delete<int>());
  std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
  std::shared_ptr<int> p6 (p5);
  std::shared_ptr<int> p7 (std::move(p6));
  std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
  std::shared_ptr<C> obj (new C);

  std::cout << "use_count:\n";
  std::cout << "p1: " << p1.use_count() << '\n';
  std::cout << "p2: " << p2.use_count() << '\n';
  std::cout << "p3: " << p3.use_count() << '\n';
  std::cout << "p4: " << p4.use_count() << '\n';
  std::cout << "p5: " << p5.use_count() << '\n';
  std::cout << "p6: " << p6.use_count() << '\n';
  std::cout << "p7: " << p7.use_count() << '\n';
  std::cout << "p8: " << p8.use_count() << '\n';
  return 0;
}

 

p1 是一个默认构造的 std::shared_ptr,它没有管理任何对象。因此,其引用计数为 0。 

p2 是通过一个指向 nullptr 的构造函数创建的 std::shared_ptr。虽然它被显式地初始化为空,但它仍然是一个没有管理任何实际对象的 shared_ptr,因此引用计数为 0。 

p3 管理一个新分配的 int 对象。因为它是唯一一个管理该对象的 shared_ptr,所以引用计数为 1。 

p4 管理一个新分配的 int 对象,并显式地指定了 std::default_delete<int>() 作为删除器。尽管提供了自定义删除器,但 p4 仍然是唯一一个管理该对象的 shared_ptr,因此引用计数为 1。 

 p5 管理一个新分配的 int 对象,并指定了一个 lambda 函数作为删除器,同时还提供了一个 std::allocator<int>()。在创建 p6 之前,p5 是唯一一个管理该对象的 shared_ptr,因此引用计数为 1。

p6 是通过复制 p5 创建的,因此它们共享同一个管理的 int 对象。复制操作增加了对象的引用计数,所以现在 p5 和 p6 都指向该对象,引用计数为 2。

但是上图打印的p6的use_count不是0吗?

那是因为p7的创建导致 p6 不再管理该对象。

 p7 是通过移动 p6 创建的,移动操作将 p6 所管理的对象的所有权转移给 p7。在 p7 创建后,p6 不再拥有该对象的所有权,但引用计数在 p6 销毁之前不会减少。正确的理解应该是,在 p7 创建后且 p6 超出作用域或被重置之前,引用计数保持为 2,但 p6 不再参与该计数(因为它已被移动)。一旦 p6 超出作用域或被重置,引用计数将减少。

std::shared_ptr的底层原理 

shared_ptr在内部维护一个引用计数,其只有两个指针成员,一个是指针所管理的数据的地址 ,还有一个指针是控制块(Control Block)的地址,包括引用计数,weak_ptr计数,删除器,分配器。

需要注意的是,因为不同的指针对象需要共享相同的内存,所以引用计数存储在堆上。

unique_ptr只有一个指针,指向所管理的数据的地址,因此它的大小也是shared_ptr的一半。

#include<iostream>
#include<memory>
int main()
{
    std::cout<<sizeof(std::shared_ptr<int>)<<std::endl;
    std::cout<<sizeof(std::unique_ptr<int>)<<std::endl;
}

下面是64位环境下的运行结果:

当我们拷贝一个shared_ptr时,引用计数加1,

当我们销毁一个shared_ptr时,引用计数减一,当引用计数减到0时,会释放shared_ptr的两个指针(即指向管理数据的指针和指向数据块的指针)。

而当我们赋值一个shared_ptr指针时,我们先递减左侧运算对象的引用计数,如果引用计数变为0,我们就释放左侧运算对象的内存以及引用计数的内存,然后拷贝右侧计算对象的拷贝指针和引用计数指针,最后递增引用计数。

下面来看我模拟实现的一个shared_ptr类:

模拟实现简单shared_ptr类

#include <iostream>  
class RefCount {  
public:  
    RefCount() : count(0) {}  
    void increment() { ++count; }  
    void decrement() {  
        if (--count == 0) {  
            delete this; 
        }  
    }  
    int& get()  { return count; }  
  
private:  
    int count;  
};  
  
template<typename T>  
class shared_ptr {  
public:  
    // 构造函数  
    shared_ptr() : _ptr(nullptr), _count(nullptr) {}  
    explicit shared_ptr(T* ptr) : _ptr(ptr), _count(new RefCount()) {  
        _count->increment();  
    }  
  
    // 拷贝构造函数和拷贝赋值运算符,增加引用计数  
    shared_ptr(const shared_ptr& other) : _ptr(other._ptr), _count(other._count) {  
        if (_count) {  
            _count->increment();  
        }  
    }  
  
    shared_ptr& operator=(const shared_ptr& other) {  
        if (this != &other) {  
            // 减少当前对象的引用计数
            if (_count && --_count->get() == 0) {  
                delete _ptr;  
                delete _count;  
            }  
  
            // 复制其他对象的指针和引用计数  
            _ptr = other._ptr;  
            _count = other._count;  
            if (_count) {  
                _count->increment();  
            }  
        }  
        return *this;  
    }  
  
    // 移动构造函数和移动赋值运算符,转移所有权
    shared_ptr(shared_ptr&& other) noexcept : _ptr(other._ptr), _count(other._count) {  
        other._ptr = nullptr;  
        other._count = nullptr;  
    }  
  
    shared_ptr& operator=(shared_ptr&& other) noexcept {  
        if (this != &other) {  
            // 减少当前对象的引用计数
            if (_count &&-- _count->get() == 0) {  
                delete _ptr;  
                delete _count;  
            }  
  
            // 转移所有权  
            _ptr = other._ptr;  
            _count = other._count;  
            other._ptr = nullptr;  
            other._count = nullptr;  
        }  
        return *this;  
    }  
  
    // 解引用和成员访问操作符  
    T& operator*() const {  
        if (!_ptr) {  
            throw std::runtime_error("shared_ptr is null");  
        }  
        return *_ptr;  
    }  
  
    T* operator->() const {  
        if (!_ptr) {  
            throw std::runtime_error("shared_ptr is null");  
        }  
        return _ptr;  
    }  
  
    // 获取原始指针
    T* get() const {  
        return _ptr;  
    }  
    int use_count() const 
    {
        if(_count) return _count->get();
        return 0;
    }
    // 检查是否为空
    bool operator!() const {  
        return _ptr == nullptr;  
    }  
  
    // 析构函数,减少引用计数并释放对象
    ~shared_ptr() {  
        if (_count && --_count->get() == 0) {  
            delete _ptr;  
            delete _count;  
        }  
    }  
  
private:  
    T* _ptr;  
    RefCount* _count;
};  
  
int main() {  
    shared_ptr<int> ptr1(new int(42));  
    std::cout << "Value: " << *ptr1 << std::endl;  
    std::cout << "Initial use_count: " << ptr1.use_count() << std::endl;  
    // 共享所有权  
    shared_ptr<int> ptr2 = ptr1;  
    std::cout << "Value after copy: " << *ptr2 << std::endl;  
    std::cout << "Use_count after copy: " << ptr1.use_count() << std::endl; 
    std::cout << "Use_count of ptr2: " << ptr2.use_count() << std::endl;   
  
    return 0;  
}

什么时候用shared_ptr

当多个对象或组件需要共享对同一动态分配资源的所有权,并且希望自动管理该资源的生命周期以避免内存泄漏时,应使用 shared_ptr。大型应用程序或系统中,不同的组件可能需要共享某些资源。使用 shared_ptr 可以方便地实现这些组件之间的资源共享,同时确保资源的正确释放。

std::unique_ptr类

和shared_ptr不同,unique_ptr独享指向的对象。

它不能拷贝构造和赋值。

    // 禁止拷贝构造函数和拷贝赋值运算符,以确保唯一所有权  
    unique_ptr(const unique_ptr&other) = delete;
    unique_ptr&operator=(const unique_ptr&other) = delete;

但是允许移动构造和移动赋值,也就是转移所有权

std::unique_ptr<int> p1 = std::make_unique<int>(0);
std::unique_ptr<int> p2 = std::move(p1); 

std::move函数可以将一个unique_ptr转移给另一个unique_ptr或者shared_ptr,转移后,原来的unique_ptr失去对内存的控制权限,变成空指针。

    // 移动构造函数和移动赋值运算符,允许所有权转移 
    unique_ptr(unique_ptr&&other) : _ptr(other._ptr) 
    {
        other._ptr=nullptr;
    }
    unique_ptr&operator=(unique_ptr&&other)
    {
        if(this!=&other)
        {
            delete _ptr;
            _ptr=other._ptr;
            other._ptr=nullptr;
        }
        return *this;
    }

下面是我模拟实现的一个unique_ptr类。

模拟实现unique_ptr类

#include<iostream>
template<typename T>
class unique_ptr
{
    public:
    unique_ptr() : _ptr(nullptr) {}
    unique_ptr(T*ptr) : _ptr(ptr) {}
    // 禁止拷贝构造函数和拷贝赋值运算符,以确保唯一所有权  
    unique_ptr(const unique_ptr&other) = delete;
    unique_ptr&operator=(const unique_ptr&other) = delete;
    // 移动构造函数和移动赋值运算符,允许所有权转移 
    unique_ptr(unique_ptr&&other) : _ptr(other._ptr) 
    {
        other._ptr=nullptr;
    }
    unique_ptr&operator=(unique_ptr&&other)
    {
        if(this!=&other)
        {
            delete _ptr;
            _ptr=other._ptr;
            other._ptr=nullptr;
        }
        return *this;
    }
    T&operator*() const
    {
        if(!_ptr)
        {
            throw std::runtime_error("unique_ptr is null");
        }
        return *_ptr;
    }
    T*operator->() const
    {
        if(!_ptr)
        {
            throw std::runtime_error("unique_ptr is null");
        }
        return _ptr;
    }
    // 获取原始指针 
    T*get()
    {
        return _ptr;
    }
    bool operator!() const {  
        return _ptr == nullptr;  
    }  
    ~unique_ptr()
    {
        delete _ptr;
    }
    private:
    T*_ptr;
};
int main() {  
    unique_ptr<int> ptr1(new int(42));  
    std::cout << "Value: " << *ptr1 << std::endl;  
  
    // 转移所有权  
    unique_ptr<int> ptr2 = std::move(ptr1);  
    if (!ptr1) {  
        std::cout << "ptr1 is now null" << std::endl;  
    }  
    std::cout << "Value after move: " << *ptr2 << std::endl;  
  
    return 0;  
}

运行结果如下。 

 

什么时候用unique_ptr 

当只有一个所有者需要管理动态分配资源的整个生命周期,并且希望确保该资源在所有者被销毁时自动释放时,使用unique_ptr。

std::weak_ptr类

std::weak_ptr是一种智能指针,它指向一个std::shared_ptr管理的对象。但是,它不会增加对象的引用计数,因此,它不会影响对象的生命周期。这种指针的主要作用是协助std::shared_ptr工作,它可以访问std::shared_ptr管理的对象,但是它不拥有该对象。std::weak_ptr可从std::shared_ptr或另一个std::weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。

#include <iostream>  
#include <memory>  
  
class B;
  
class A {  
public:  
    std::shared_ptr<B> ptrB;  
    ~A() { std::cout << "A destroyed" << std::endl; }  
};  
  
class B {  
public:  
    std::shared_ptr<A> ptrA;  
    ~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->ptrB = b;  
        b->ptrA = a;  
  
    return 0;  
}

在上面的代码中,ab的引用计数都是2,因为它们相互引用。当局部作用域结束时,我们期望ab被销毁,但由于循环引用,它们实际上并没有被销毁,导致内存泄漏。

使用weak_ptr解决循环引用

#include <iostream>  
#include <memory>  
  
class B; // 前向声明  
  
class A {  
public:  
    std::weak_ptr<B> ptrB; // 使用weak_ptr来打破循环引用  
    ~A() { std::cout << "A destroyed" << std::endl; }  
};  
  
class B {  
public:  
    std::shared_ptr<A> ptrA;  
    ~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->ptrB = b; // a现在持有一个weak_ptr到b  
        b->ptrA = a; // b仍然持有一个shared_ptr到a  
    }
    std::cout << "Shared pointers have been destroyed correctly." << std::endl;  
    
    return 0;  
}

在这个修改后的代码中,A类中的ptrB是一个weak_ptr,它不会增加B对象的引用计数。因此,当ab都离开作用域时,它们的引用计数都会降为零,并且它们所管理的资源会被正确释放。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈大大陈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值