智能指针内容大总结

智能指针是为了解决动态分配内存导致内存泄露和多次释放同⼀内存所提出的,C11标准中放 在< memory>头⽂件。包括:共享指针,独占指针,弱指针

不带引用计数

auto\scoped\unique

1.auto

只有最后一个auto_ptr持有资源,原来的都被赋值nullptr,
不能用在容器中,不建议使用除非场景很简单

2.scoped

私有化拷贝构造函数和赋值函数,从根本上杜绝浅拷贝的发生
所以scoped_ptr也是不能用在容器当中的,如果容器互相进行拷贝或者赋值,就会引起scoped_ptr对象的拷贝构造和赋值,这是不允许的,代码会提示编译错误

总结:auto_ptr可以任意转移资源的所有权,而scoped_ptr不会转移所有权

3.unique

去掉了拷贝构造函数和operator=赋值重载函数,防止智能指针浅拷贝问题的发生但是unique_ptr提供了带右值引用参数的拷贝构造和赋值??用处是什么
最终也是只能有一个该智能指针引用资源,因此建议在使用不带引用计数的智能指针时,可以优先选择unique_ptr智能指针

带引用计数

允许多个智能指针指向同一个资源
shared\weak
shared_ptr和weak_ptr底层的引用计数已经通过CAS操作,保证了引用计数加减的原子特性,因此shared_ptr和weak_ptr本身就是线程安全的带引用计数的智能指针

1.shared

  1. 构造函数中计数初始化为1;
  2. 拷贝构造函数中计数值加1;
  3. 赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一;
  4. 析构函数中引用计数减一;
  5. 在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象

2.weak:

弱智能指针weak_ptr区别于shared_ptr之处在于:
1.weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在
2.weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
3.weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock方法提升为shared_ptr强智能指针,才能访问资源
定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr

手写一个shared_ptr

1)⼀个模板指针T* ptr
指向实际的对象
2)⼀个引⽤次数
必须new出来的,不然会多个shared_ptr⾥⾯会有不同的引⽤次数⽽导致多次delete
3)重载operator*和operator->
使得能像指针⼀样使⽤shared_ptr
4)重载copy constructor
使其引⽤次数加⼀(拷贝构造函数)
5)重载operator=(赋值运算符)
如果原来的shared_ptr已经有对象,则让其引⽤次数减⼀并判断引⽤是否为零(是否调⽤
delete),然后将新的对象引⽤次数加⼀
6)重载析构函数
使引⽤次数减⼀并判断引⽤是否为零; (是否调⽤delete)

template<typename T>
class shared_ptr {
public:
    shared_ptr() : ptr(nullptr), ref_count(nullptr) {}
    shared_ptr(T* ptr) : ptr(ptr), ref_count(new int(1)) {}
    shared_ptr(const shared_ptr<T>& other) : ptr(other.ptr), ref_count(other.ref_count) {
        if (ref_count) {
            ++(*ref_count);
        }
    }
    
    ~shared_ptr() {
        release();
    }
    
    shared_ptr<T>& operator=(const shared_ptr<T>& other) {
        if (this != &other) {
            release();
            ptr = other.ptr;
            ref_count = other.ref_count;
            if (ref_count) {
                ++(*ref_count);
            }
        }
        return *this;
    }
    
    T* get() const {
        return ptr;
    }
    
    void reset() {
        release();
        ptr = nullptr;
        ref_count = nullptr;
    }
    
    int use_count() const {
        return ref_count ? *ref_count : 0;
    }
    
    T& operator*() const {
        return *ptr;
    }
    
    T* operator->() const {
        return ptr;
    }
    
private:
    void release() {
        if (ref_count && --(*ref_count) == 0) {
            delete ptr;
            delete ref_count;
            ptr = nullptr;
            ref_count = nullptr;
        }
    }
    
private:
    T* ptr;
    int* ref_count;
};

shared_ptr引用计数存在哪里

shared_ptr的内部实现原理
堆上分配
shared_ptr从基类_Ptr_base 继承了如下成员变量(部分源码):

template<class _Ty>
class _Ptr_base
{
private:
    element_type * _Ptr{ nullptr };      //指向资源
    _Ref_count_base * _Rep{ nullptr };   //指向资源引用计数
}
_Ptr指向资源,_Rep指向资源引用计数。
_Ref_count_base的定义如下
class __declspec(novtable) _Ref_count_base
{	// common code for reference counting
private:
	_Atomic_counter_t _Uses;   //记录了引用资源的shared_ptr的个数
	_Atomic_counter_t _Weaks;  //记录了weak_ptr的个数
}

shared_ptr对象有指向资源的指针,有指向引用计数的指针
shared_ptr的构造函数中会开辟新的引用计数的资源
拷贝构造函数没有开辟新的引用计数的资源,只是引用计数加1

智能指针的线程安全问题

1.引用计数是线程安全的
存储在堆上,多个shared_ptr指向同一个堆地址,而且引用计数是原子类型。
2.在指针指向变动的时候,涉及到线程安全问题
多线程代码操作同一个shared_ptr(传引用),需要修改指向,然后原先的引用计数要减一,新指向要加1。
这几步不是原子操作。可能在减一的时候,指向已经被修改过了,引起对象的析构,后续使用coredump
操作不同shared_ptr是安全的(各自是不同的对象)

自定义删除器

1.定义个函数对象

2.lambda表达式

shared_ptr怎么实现一个普通指针的const T*效果?

要实现shared_ptr的普通指针的const T*效果,可以使用std::shared_ptr类型的智能指针。
下面是一个示例:

#include <iostream>
#include <memory>

int main() {
    int value = 42;

    // 使用 shared_ptr<const int> 创建一个指向常量的智能指针
    std::shared_ptr<const int> ptr = std::make_shared<const int>(value);

    // 无法修改指针指向的值,因为它是一个常量
    // ptr->value = 10;  // 编译错误

    // 无法重新分配指针的值,因为它是一个常量指针
    // ptr.reset(new int(100));  // 编译错误

    // 通过 shared_ptr<const int> 可以获得一个 const int* 指针
    const int* rawPtr = ptr.get();

    std::cout << *rawPtr << std::endl;  // 输出:42

    return 0;
}

在上述示例中,使用std::shared_ptr创建了一个指向常量int的智能指针ptr。这意味着指针指向的值是不可修改的。通过ptr.get()可以获取一个const int*类型的指针rawPtr,并且不能修改rawPtr指向的值。

注意,当使用shared_ptr时,指针指向的值是不可修改的,但指针本身是可修改的。如果希望同时将指针和指针指向的值都设为常量,可以使用const std::shared_ptr。

智能指针交叉引用问题怎么解决

相互引用,出作用域引用计数不为0,内存泄漏
定义对象的时候用强智能指针shared_ptr,引用对象的时候用弱智能指针weak_ptr,当通过weak_ptr访问对象成员时,需要先调用weak_ptr的lock提升方法,把weak_ptr提升成shared_ptr强智能指针,再进行对象成员调用。

enable_shared_from_this和shared_from_this机制

需要返回智能指针的类继承enable_shared_from_this
如果一个类继承了enable_shared_from_this,那么它产生的对象就会从基类enable_shared_from_this继承一个成员变量_Wptr,当定义第一个智能指针对象的时候shared_ptr< A > ptr1(new A()),调用shared_ptr的普通构造函数,就会初始化A对象的成员变量_Wptr,作为观察A对象资源的一个弱智能指针观察者

shared_from_this通过当前对象的成员变量_Wptr构造一个shared_ptr出来, shared_ptr构造调用_construct_from_weak(),会判断引用计数是否存在再提升

综上所说,所有过程都没有再使用shared_ptr的普通构造函数,没有在产生额外的引用计数对象,不会存在把一个内存资源,进行多次计数的过程;更关键的是,通过weak_ptr到shared_ptr的提升,还可以在多线程环境中判断对象是否存活或者已经析构释放,在多线程环境中是很安全的,通过this裸指针进行构造shared_ptr,不仅仅资源会多次释放,而且在多线程环境中也不确定this指向的对象是否还存活

return shared_from_this()

智能指针的使用场景

C++ 三种智能指针的使用场景_c++_行者孙_InfoQ写作社区
● unique_ptr 把握 unique 含义,资源的所有权不是共享的,而是被某个对象独占的时候。当资源所有权发生转移,可以通过 move 或者 release 进行转移(拥有对象所有权,类似于C指针保存堆上的对象)
● shared_ptr 把握 share 的含义,对象的所有权是共享的,可以被拷贝(作为需要保存在容器里的对象)
● weak_ptr 把握 weak 的含义,是一种“弱指针”,就是指向的资源可能是不可用的(可能是个垂悬指针),需要通过lock() 或者expire() 方法检查,利用这个特性,在可能会失效的场合可以使用(例如缓存、订阅者等)。需要搭配shared_ptr使用,并且可以防止循环引用。

auto_ptr为什么被弃用

auto_ptr被弃用的主要原因是它的所有权转移比较困难,容易出现悬挂指针的问题,同时也无法满足更加严格的内存管理要求。
如果两个auto_ptr指针指向同一个对象时,当该对象的生存周期结束后,系统会调用析构函数,这样导致的结果是程序对同一个对象删除了2次,造成程序出错。
那么该如何解决这个问题呢?这个问题看似很难解决,其实解决的方法却有很多:
1、通过定义赋值运算符,实现深拷贝,从而两个指针指向不同的两个对象,其中一个对象是另一个对象的副本;
2、将对象的删除所有权设置成唯一。即只允许一个对象拥有删除对象的权力,其他指针只能指向该对象,不享有删除对象的权力。auto_ptr、unique_ptr都是用这样的机制,但是后者比前者更加的严格;
3、采用引用计数的方法。当有一个一个指针指向对象时,引用计数加1,当指向该对象的指针销毁时,引用计数减1,当且仅当最后一个指向该对象的指针销毁时,也就是引用计数为0时,对象才能够被删除。智能指针shared-ptr就是采用该机制来实现的。
auto_ptr导致的问题在于,当所有权转让之后,原来的指针被用来访问原来指向的对象,当程序访问该指针指向的内容时,会发现这是一个悬空指针,程序就会出错,但是如果换成shared_ptr程序就能正常运行。

std::make_unique和std::make_shared,而非直接使用new,这样做的原因是什么;什么情况下不能使用make?

《Effective Modern C++》条款二十一:优先选用std::make_unique和std::make_shared,而非直接new
1.代码量更小
2.可能因为异常导致内存泄漏,例如在将new指针赋值智能指针前,程序发生异常,导致赋值失败,则刚刚new出来的指针将无法得到正常释放,从而内存泄漏。
3.分配一次内存,性能更高
make:
不允许自定义析构器
不直接接受大括号初始化
● 相比于直接使用new表达式,make系列函数消除了重复代码,改进了异常安全性,并且对于std::make_shared及std::allcoated_shared而言,生成的目标代码尺寸更小,速度更快。
● 不适用于使用make系列函数的场景包括:自定义析构器、以及期望直接传递大括号初始化物
● 对于std::shared_ptr,不建议使用make系列函数的额外场景包括:(1)自定义内存管理的类(2)内存紧张的系统、非常大的对象以及存在生存期更长的std::weak_ptr的std::shared_ptr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

疏狂似风~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值