当前位置: 首页> 游戏> 网游 > C++ 深入智能指针

C++ 深入智能指针

时间:2025/7/12 4:45:36来源:https://blog.csdn.net/FDS99999/article/details/141780828 浏览次数:1次

智能指针的三个常用函数:

  1. get() 获取智能指针托管的指针地址
  2. release() 取消智能指针对动态内存的托管
  3. reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉

auto_ptr

auto_ptr 的实现原理其实就是RAII,在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载,使用起来就像普通的指针。

std::auto_ptr<ClassA> pa(new ClassA);

但是由于其构造函数声明为explicit的,因此不能通过隐式转换来构造,只能显式调用构造函数。

特点
1.auto_ptr 不能共享所有权,即不要让两个auto_ptr指向同一个对象。
2.auto_ptr 不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。
3.auto_ptr 只是一种简单的智能指针,如有特殊需求,需要使用其他智能指针,比如 share_ptr。
4.auto_ptr 不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中 auto_ptr 会传递所有权,那么 source 与 sink 元素之间就不等价了。

C++11 后auto_ptr 已经被“抛弃”,已使用 unique_ptr 替代!C++11后不建议使用auto_ptr(原因):
1). 复制或者赋值都会改变资源的所有权;
2). 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值;
3). 不支持对象数组的内存管理;

unique_ptr

特点:独享它指向的对象。也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁。、

unique_ptr 和 auto_ptr 用法几乎一样,除了一些特殊特性:

1.基于排他所有权模式:两个指针不能指向同一个资源
2.无法进行左值 unique_ptr 复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
3.保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象;
4.在容器中保存指针是安全的;

典型用途:
1.作为一个类的成员变量,这个变量只在本类使用,不会被赋值给其他类,也不会作为参数传递给某个函数;
2.在一个函数作为局部变量,使用完就不用再管,函数结束,自动释放托管资源;

原理
1.构造时传入托管对象的指针,析构时delete对象;
2.禁用赋值函数;

unique_ptr 手写

#include <utility>
#include<iostream>/***** 智能指针unique_ptr的简单实现* * 特点:独享它指向的对象。也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁* * 典型用途:* 1. 在一个函数定义一个A* ptr = new A(), 结束还需要用delete,而用unique_ptr,就不需要自己调用delete* 2. 作为一个类的变量,这个变量只在本类使用,不会被其他类调用,也不会作为参数传递给某个函数* */
template<typename T>
class unique_ptr
{
private:T * ptr_resource = nullptr;public://explicit构造函数是用来防止隐式转换, 即不允许写成unique_ptr<T> tempPtr = T;//std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.//move之后,raw_resource内部的资源将不能再被raw_resource使用explicit unique_ptr(T* raw_resource) noexcept : ptr_resource(std::move(raw_resource)) {}unique_ptr(std::nullptr_t) : ptr_resource(nullptr) {}unique_ptr() noexcept : ptr_resource(nullptr) {}//析构时, 释放托管的对象资源~unique_ptr() noexcept{delete ptr_resource;}// Disables the copy/ctor and copy assignment operator. We cannot have two copies exist or it'll bypass the RAII concept.//重要,禁止两种拷贝的赋值方式//使用"=delete"修饰,表示函数被定义为deleted,也就意味着这个成员函数不能再被调用,否则就会出错。unique_ptr(const unique_ptr<T>&) noexcept = delete;unique_ptr& operator = (const unique_ptr&) noexcept = delete;public://&& 是右值引用,见https://zhuanlan.zhihu.com/p/107445960// 允许移动语义。虽然无法复制unique_ptr,但可以安全地移动。//例子:unique_ptr<Test> tPtr3(std::move(tPtr1));unique_ptr(unique_ptr&& move) noexcept{std::cout << "construct for unique_ptr&&" << std::endl;move.swap(*this);}// ptr = std::move(resource)unique_ptr& operator=(unique_ptr&& move) noexcept{std::cout << "operator= for unique_ptr&&" << std::endl;move.swap(*this);return *this;}explicit operator bool() const noexcept{return this->ptr_resource;}// releases the ownership of the resource. The user is now responsible for memory clean-up.T* release() noexcept{return std::exchange(ptr_resource, nullptr);}// returns a pointer to the resourceT* get() const noexcept{return ptr_resource;}// swaps the resourcesvoid swap(unique_ptr<T>& resource_ptr) noexcept{std::swap(ptr_resource, resource_ptr.ptr_resource);}// reset就删除老的,指向新的void reset(T* resource_ptr) noexcept(false){// ensure a invalid resource is not passed or program will be terminatedif (resource_ptr == nullptr)throw std::invalid_argument("An invalid pointer was passed, resources will not be swapped");delete ptr_resource;ptr_resource = nullptr;std::swap(ptr_resource, resource_ptr);}
public:// overloaded operatorsT * operator->() const noexcept{return this->ptr_resource;}T& operator*() const noexcept{return *this->ptr_resource;}// 额外说明noexcept//noexcept C++11关键字, 告诉编译器,函数中不会发生异常,有利于编译器对程序做更多的优化//C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化
};
#include <utility>template <typename T>
class unique_ptr {
public:explicit unique_ptr(T* ptr = nullptr): ptr_(ptr) {}~unique_ptr(){delete ptr_;}unique_ptr(unique_ptr&& other){ptr_ = other.release();}// 子类指针向基类指针的转换template <typename U>unique_ptr(unique_ptr<U>&& other){ptr_ = other.release();}unique_ptr& operator=(unique_ptr rhs){rhs.swap(*this);return *this;}T* release(){T* ptr = ptr_;ptr_ = nullptr;return ptr;}void swap(unique_ptr& rhs){using std::swap;swap(ptr_, rhs.ptr_);}T* get() const { return ptr_; }T& operator*() const { return *ptr_; }T* operator->() const { return ptr_; }operator bool() const { return ptr_; }
private:T* ptr_;
};

shared_ptr

共享对其所指堆内存空间的所有权,通过引用计数技术来追踪指向动态分配内存对象的所有者数量,当最后⼀个指涉到该对象的 shared_ptr 不再指向他时,shared_ptr会⾃动析构所指对象,其所指向的动态分配内存也会随之释放,有效地避免了内存泄漏问题。

核心特性
1.共享所有权:一个std::shared_ptr实例可以被复制或移动到另一个std::shared_ptr实例,复制后两者会共享同一份资源,并且都参与到引用计数的维护中。
2.弱引用std::weak_ptr,它是一种弱引用,不会增加引用计数,用于解决循环引用导致的对象无法释放的问题。
3.自定义删除器std::shared_ptr允许指定自定义的删除器,在资源不再需要时执行特定的清理操作。
4.原子性:在多线程环境下,引用计数的增减操作是原子性的,确保了线程安全。

shared_ptr除了有一个指针,指向所管理数据的地址,还有一个指针指向一个控制块的地址,里面存放了所管理数据的数量(常说的引用计数)、weak_ptr 的数量、删除器、分配器等。

shared_ptr 是一个模板类,所以对于shared_ptrT的并发操作的安全性,也会被纳入讨论范围。因此造成了探讨其线程安全性问题上的复杂性。

手写 shared_ptr

实现一个简单的 shared_ptr类,主要两个成员变量:
1.指向对象的指针:用于保存要管理的对象的地址。
2.引用计数:用于记录当前有多少个shared_ptr共享同一个对象,引用计数需要使用指针来在多个shared_ptr 对象之间共享计数,实际上比这个复杂,一般是一个类对象,内部包含多个引用计数相关的信息。

template<typename T>
class myShared_ptr {
public://use_count初始化的意思是,如果初始化shared_ptr的是一个空指针,那么开始引用计数就是0。如果是一个有效指针,那么开始引用计数就是1// 构造函数要设计成explicit的,防止隐式的类类型转换,因为普通指针是不能直接赋值给智能指针的。必须使用直接初始化(用括号)的方式才行。explicit myShared_ptr(T* ptr = nullptr):use_count(ptr == nullptr?nullptr:new int (1)),_ptr(ptr){}// 在拷贝构造函数中,将指向对象的指针和引用计数成员变量复制到新对象,并递增引用计数。myShared_ptr(const myShared_ptr& other_ptr):use_count(other_ptr.use_count),_ptr(other_ptr._ptr){if(use_count != nullptr)//防止用空指针拷贝构造{(*use_count) ++;}}// 在拷贝赋值运算符中,处理自我赋值情况并更新引用计数myShared_ptr& operator=(const myShared_ptr& other_ptr){if(&other_ptr != this)//防止自身赋值,自身赋值的话就不用管,引用计数就不用加一了{release();//先将自身计数减一_ptr = other_ptr._ptr;use_count = other_ptr.use_count;//(*use_count) ++;//这里要注意不能直接++,防止other_ptr是空指针if(use_count)++ (*use_count);}return *this;}~ myShared_ptr(){release();}T& operator*()const//重载解引用运算符{return *_ptr;//返回被管理对象的引用}T* operator->()const//重载->运算符{return _ptr;}T* get()const//和->一样,返回的是指针{return _ptr;}int get_use_count() const{return use_count != nullptr?(*use_count):0;//如果use_count是空指针的话,就说明没有引用计数,说明shared_ptr是空的,就返回0}private:int* use_count;//引用计数T* _ptr;//指向所管理的对象// 析构函数:在析构函数中处理引用计数的递减和内存的释放。void release(){if(use_count && -- (*use_count) == 0){delete use_count;delete _ptr;}}
};

weak_ptr

std::weak_ptr是C++ 11引入的一种弱引用智能指针,它不拥有所指向对象的所有权,而是对shared_ptr持有的对象提供一种非拥有但可观察的访问方式。
weak_ptr 主要用于打破共享所有权循环引用的问题,防止出现内存泄漏。

核心特性
1.不增加引用计数
当创建一个weak_ptr时,它不会增加其所指向的对象的引用计数。这意味着,即使有多个weak_ptr指向同一对象,只要没有对应的shared_ptr存在,该对象仍会在所有shared_ptr释放后被正确销毁。
2.检查有效性
通过调用weak_ptrlock()成员函数,可以获取一个指向同一对象的shared_ptr。如果此时对象已被删除,则返回的shared_ptr为空。因此,在使用weak_ptr之前通常需要先调用lock()来检查对象是否仍然有效。
3.不阻止对象析构
由于weak_ptr仅提供了对目标对象的弱引用,所以在没有活跃的shared_ptr指向该对象时,对象会被正常回收。

应用场景:
1、打破循环引用;
2、缓存与观察者模式:在设计缓存系统或者观察者模式时,某个对象可能需要知道另一个对象的状态变化,但并不希望影响该对象的生命周期。这时,可以使用weak_ptr来跟踪对象,而不增加其引用计数。

weak_ptr 手写

template<typename T>
class _weak_ptr{friend class _shared_ptr<T>;public:    _weak_ptr():use_count(new int(0)),ptr(nullptr){}_weak_ptr(const _weak_ptr& wp):use_count(wp?wp.use_count:new int(0)),ptr(wp.ptr){}_weak_ptr(const _shared_ptr<T>& sp):use_count(sp.use_count),ptr(sp._ptr){}_weak_ptr& operator=(const _weak_ptr& wp){if(&wp != this){ptr = wp.ptr;use_count = wp.use_count;}return *this;}_weak_ptr& operator=(const _shared_ptr<T>& sp){ptr = sp._ptr;use_count = sp.use_count;return *this;}T* operator->(){return ptr;}T& operator*(){return *ptr;}int get_use_count(){//return *use_count;return use_count == nullptr?0:(*use_count);}bool expire()//根据use——count是否为零来判断{return use_count == nullptr || *(use_count) == 0;}_shared_ptr<T>& lock(){if(expire())//如果expire为true,就返回一个空的shared——ptr指针return _shared_ptr<T>();return _shared_ptr<T>(*this);}void reset()//将weak_ptr置为空{ptr = nullptr;use_count = nullptr;}
private:int* use_count;T* ptr;
};

智能指针的缺点以及智能指针引发的问题

1.性能开销:智能指针需要进行额外的内存管理和引用计数操作,这可能会导致程序的性能下降。相比于原始指针,智能指针需要更多的计算资源和时间来完成内存管理任务。
2.循环引用:如果智能指针被用于管理对象之间的循环引用,就可能会出现内存泄漏的问题。当两个对象相互引用时,它们的引用计数永远不会达到零,因此它们的内存也永远不会被释放。
3.难以调试:由于智能指针管理的内存是自动分配和释放的,因此在程序运行时,很难确定哪个指针指向哪个内存块,以及哪个指针可能导致内存泄漏或悬挂指针等问题。这使得调试非常困难。
4.不适用于某些场景:智能指针通常适用于单线程环境,但在某些多线程或异步环境中,智能指针的使用可能会导致竞态条件或死锁等问题。此外,智能指针也不适用于需要在不同的进程之间共享内存的场景。

当我们谈论shared_ptr的线程安全性时,我们在谈论什么?
深入理解shared_ptr与weak_ptr之手写
智能指针有什么不足之处?
C++ 11新特性之week_ptr
C++ 智能指针unique_ptr原理与自定义实现

关键字:C++ 深入智能指针

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: