c++ shared_ptr和unique_ptr实现

C++智能指针

目录

1. 简要介绍

智能指针是为了更加安全的使用动态内存,与常规指针的区别是可以自动释放所指向的对象。是RAII思想的一个很好的例子。

智能指针是类模板,把普通指针交给智能指针对象来进行管理。

2. 为什么要使用智能指针(普通指针的不足)

为了更加安全的使用动态内存

  1. new和new[]的内存需要使用delete和delete[]来进行释放
  2. 程序员可能失误忘记释放内存
  3. 程序员也不确定何时释放内存,例如在多线程问题中
3. 智能指针的优势

为了更安全的使用堆区的动态内存避免出现内存泄漏的问题,c++中引入了多种智能指针。智能指针是一个类模板,可以把普通指针交给智能指针对象来进行管理。在超出指针作用域时,智能指针会自动调用指针的析构函数来释放资源,避免了内存泄漏的问题。
unique_ptr

所有的智能指针都在 #include<memory>头文件中

unique_ptr独占其指向的对象,也就是说,同时只有一个unique_ptr指向同一个对象,当这个**unique_ptr**被销毁的时候,指向的对象也被销毁

4. **unique_ptr**使用注意事项

下面这些是在使用**shared_ptr**的时候也要注意的

  • 不要用一个裸指针初始化多个unique_ptr对象,避免多次调用析构函数,出现野指针的情况
  • 不支持(+、-、++、—)等运算符操作

unique_ptr参数传递的时候,可以传引用(因为拷贝构造函数被禁用了),也可以调用get()函数传递裸指针

unique_ptr不是绝对安全

shared_ptr

shared_ptr**共享它指向的对象,多个shared_ptr可以关联相同的对象,在内部采用引用计数机制**来实现。

shared_ptr的构造函数也是explicit,但是没有删除拷贝构造函数和赋值函数。(与unique_ptr的不同)

另外的不同点就是,在c++11版本中,就提供了make_shared函数

如果unique_ptr能解决问题,就不要使用shared_ptrunique_ptr的效率更高,占用的资源更少。

shared_ptr可能存在的问题

shared_ptr内部维护了一个共享的引用计数器,多个shared_ptr可以指向同一个资源

如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放

weak_ptr

weak_ptr是为了配合shared_ptr而引入的,它指向一个由shared_ptr管理的资源但不影响资源生命周期,也就是说,将一个**weak_ptr绑定到一个shared_ptr不会改变shared_ptr**的引用计数。

不论是否由weak_ptr指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放

5. 引用计数的致命问题

引用计数是计算机编程语言中的一种内存管理技术,当管理对象的引用次数变成0时,就释放资源

Python中使用引用计数的方式来管理内存

01循环引用

C++中的循环引用主要是在智能指针shared_ptr的使用中导致的,解决方法是使用weak_ptr指向shared_ptr管理的资源

6. 智能指针是线程安全的吗

智能指针是线程安全的,但是智能指针管理的资源不是线程安全的,需要自己手动控制

修改引用计数的操作是原子的

规则

  1. 同一个shared_ptr并发读时,是安全的,但是写是不安全的
  2. 共享引用计数shared_ptr被并发读写时,都是安全的

智能指针写操作:修改原始指针的指向,如swap() reset()和析构函数,智能指针中有两个成员,一个原始指针一个引用计数

读操作只是把智能指针赋值给另一个智能指针,这个不会修改原始指针的指向,只会修改引用计数,所以是线程安全的。

写操作有两个事情,先修改原始指针,然后再修改引用计数,这两个步骤是没有加锁保护的,所以两个操作加起来就不是原子

7. std::move()函数在什么情况下会失效

move()函数失效意味着没有按照预期调用移动构造函数,而是调用了拷贝构造函数

std::move()是否可以保证一定能移动成功?

答案显然是否定的

A a = std::move(b);

本质上是先将b强制转化了右值引用A&&, 然后触发了移动构造函数,在移动构造函数中,完成了对象b到对象a的移动。

例如

const std::string str = "123";
std::string str2(std::move(str));

以上,对str对象调用std::move,强转出的类型是const string && ,而不是 string &&,这样移动构造函数就不会起作用了,但是这个类型可以令复制构造函数生效。

7. 实现一个自己的shared_ptr

以下是**shared_ptr**的简单实现

#include <iostream>

template <typename T>
class shared_ptr {
public: 
    // 构造函数,传入一个指针对象
    explicit shared_ptr(T* ptr = nullptr) : ptr_(ptr), count_(new size_t(1)) {}

    // 拷贝构造函数,拷贝指针和计数器
    shared_ptr(const shared_ptr<T>& other) : ptr_(other.ptr_), count_(other.count_) {
        // 使用指针是因为
        (*count_) ++;
    }

    // 析构函数,当计数器为0时释放内存
    ~shared_ptr() {
        (*count) --;
        if (*count == 0) {
            delete ptr_;
            ptr_ = nullptr;
            delete count_;
            count = nullptr;
        }
    }

    // 重载赋值运算符,拷贝指针和计数器
    shared_ptr<T>& operator=(const shared_ptr<T>& other) {
        if (this != &other) {
            // 原来管理的资源引用计数减少,如果减为0,则销毁资源
            (*count_) --;
            if (*count_ == 0) {
                delete ptr_;
                ptr_ = nullptr;
                delete count_;
                count = nullptr;
            }
            // 新资源的引用计数增加,拷贝指针和计数器
            ptr_ = other.ptr_;
            count_ = other.count_;
            (*count_) ++;
        }
        return *this;
    }

    // 重载->操作符,返回指针
    T* operator->() const {
        return ptr_;
    }

    // 重载*操作符,返回引用
    T& operator*() const {
        return *ptr_;
    }

    // 返回计数器的值
    size_t use_count() const {
        return *count_;
    }

private:
    T* ptr_;
    size_t* count_;
}

以上是一个简单的shared_ptr的实现,其中计数器采用动态分配内存的方式,每个shared_ptr对象的计数器指向同一个内存区域,以确保引用计数的正确性。

shared_ptr对象被拷贝时,计数器+1;当shared_ptr对象被析构时,计数器-1。当计数器的值为0时,释放指针和计数器所指向的内存。

8. 实现一个自己的unique_ptr

需要注意的知识点

  • 异常
  • unique_ptr本身功能
  • 三五法则和阻止拷贝
  • 隐式的类类型转换
  • 移动构造、移动赋值及自赋值问题
  • 编写模板类
#include <iostream>

template<typename T>
class MyUniquePtr {
public:
    explicit MyUniquePtr(T* ptr = nullptr) : mPtr(ptr) {}

    ~MyUniquePtr() {
        if (mPtr) {
            delete mPtr;
            mPtr = nullptr;
        }
    }

    // 移动构造函数和移动赋值
    MyUniquePtr(MyUniquePtr &&p) noexcept : mPtr(p.mPtr){
        p.mPtr = nullptr;
    }
    MyUniquePtr& operator=(MyUniquePtr &&p) noexcept {
        swap(*this, p);
        return *this;
    }

    // 禁用拷贝构造函数和重载
    MyUniquePtr(const MyUniquePtr & p) = delete;
    MyUniquePtr& operator=(const MyUniquePtr &p) = delete;

    T& operator*() const noexcept {
        return *mPtr;
    }

    T* operator->() const noexcept {
        return mPtr;
    }

    explicit operator bool() const noexcept {
        return mPtr;
    }

    // 重新设置unique_ptr管理的资源
    void reset(T* q = nullptr) noexcept {
        if (q != mPtr) {
            if (mPtr)
                delete mPtr;
            mPtr = q;
        }
    }

    // release() ,释放资源,并返回指针
    T* release() noexcept {
        T* res = mPtr;
        mPtr = nullptr;
        return res;
    }

    T* get() const noexcept {
        return mPtr;
    }

    void swap(MyUniquePtr & p) noexcept {
        using std::swap;
        swap(mPtr, p.mPtr);
    }

private:
    T* mPtr;
}
参考链接:

https://cloud.tencent.com/developer/article/1516496

https://juejin.cn/s/shared_ptr实现面试

https://www.jianshu.com/p/77c2988be336

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值