返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >融会贯通C++智能指针教程
  • 248
分享到

融会贯通C++智能指针教程

2024-04-02 19:04:59 248人浏览 八月长安
摘要

目录一、基础知识介绍裸指针常出现以下几个问题:二、不带引用计数的智能指针不带引用计数的智能指针主要包括(1)auto_ptr源码(2)scoped_ptr(3)unique_ptr源

一、基础知识介绍

裸指针常出现以下几个问题:

  • 忘记释放资源,导致资源泄露(常发生内存泄漏问题)
  • 同一资源释放多次,导致释放野指针,程序崩溃
  • 写了释放资源的代码,但是由于程序逻辑满足条件,执行中间某句代码时程序就退出了,导致释放资源的代码未被执行到
  • 代码运行过程中发生异常,随着异常栈展开,导致释放资源的代码未被执行到

template<typename T> 
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr):_ptr(ptr) {}
    ~SmartPtr() {delete _ptr;}
private:
    T* _ptr;
};
int main(){
    SmartPtr<int> ptr(new int);
    return 0;
}

上面这段代码就是一个非常简单的智能指针,主要用到了这两点:

手动实现智能指针体现在把裸指针进行了面向对象的封装,在构造函数中初始化资源地址,在析构函数中负责释放资源

利用栈上的对象出作用域自动析构这个特点,在智能指针的析构函数中保证释放资源。

所以,智能指针一般都是定义在栈上的

面试官:能不能在堆上定义智能指针?
答:不能。就好比SmartPtr<int>* ptr = new SmartPtr<int>();这段代码中,在堆空间定义一个智能指针,

这依然需要我们手动进行delete,否则无法堆空间的对象无法释放,因为堆空间的对象无法自动调用析构函数。

一般而言智能指针还需要提供裸指针常见的*->两种运算符的重载函数:


    const T& operator*() const{return *_ptr;}
    T& operator*(){return *_ptr;}
    const T operator->() const{return _ptr;}
    T operator->(){return _ptr;}

二、不带引用计数的智能指针


int main(){
    SmartPtr<int> ptr1(new int);
    SmartPtr<int> ptr2(ptr1);
    return 0;
}

以上代码运行时,由于ptr2拷贝构造时默认是浅拷贝,会出现同一资源释放两次的错误(释放野指针),这里需要解决两个问题:

智能指针的浅拷贝

多个智能指针指向同一个资源的时候,怎么保证资源只释放一次,而不是每个智能指针都释放一次

不带引用计数的智能指针主要包括

auto_ptrscoped_ptrunique_ptr

(1)auto_ptr源码


auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}
_Ty* release() noexcept {
    _Ty* _Tmp = _Myptr;
    _Myptr    = nullptr;
    return _Tmp;
}

使用auto_ptr


	auto_ptr<int> ptr1(new int);
    auto_ptr<int> ptr2(ptr1);

从源码可以看到,auto_ptr底层先是将ptr1置空,然后将指向的资源再给ptr2auto_ptr所做的就是使最后一个构造的指针指向资源,以前的指针全都置空,如果再去访问以前的指针就是访问空指针了,这很危险。所以一般不使用auto_ptr

(2)scoped_ptr

该智能指针底层私有化了拷贝构造函数和operator=赋值函数,

从根本上杜绝了智能指针浅拷贝的发生,所以scoped_ptr也是不能用在容器当中的。

  • 如果容器互相进行拷贝或者赋值,就会引起scoped_ptr对象的拷贝构造和赋值,这是不允许的,代码会提示编译错误。
  • auto_ptrscoped_ptr这一点上的区别,有些资料上用所有权的概念来描述,道理是相同的。
  • auto_ptr可以任意转移资源的所有权,而scoped_ptr不会转移所有权(因为拷贝构造和赋值被禁止了)

一般也不推荐使用scoped_ptr

(3)unique_ptr源码


	template <class _Dx2 = _Dx, enable_if_t<is_move_constructible_v<_Dx2>, int> = 0>
    unique_ptr(unique_ptr&& _Right) noexcept
        : _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx>(_Right.get_deleter()), _Right.release()) {}	
	unique_ptr(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
        : _Mypair(_One_then_variadic_args_t{}, _STD forward<_Dx2>(_Right.get_deleter()), _Right.release()) {}	
	// 拷贝构造或者赋值运算符的时候,用于将以前的智能指针置空
	pointer release() noexcept {
        return _STD exchange(_Mypair._Myval2, nullptr);
    }	
	unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;

从源码可以看到,unique_ptr直接delete了拷贝构造函数和operator=赋值重载函数

禁止用户对unique_ptr进行显示的拷贝构造和赋值,防止智能指针浅拷贝问题的发生

但是unique_ptr提供了带右值引用参数的拷贝构造和赋值

即unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作


    unique_ptr<int> ptr1(new int);
    unique_ptr<int> ptr2(std::move(ptr1));// 使用右值引用的拷贝构造,由于执行了release,ptr1已经被置空
    cout << (ptr1 == nullptr) << endl;    // true
    ptr2 = std::move(ptr1);               // 使用右值引用的operator=赋值重载函数
    cout << (ptr2 == nullptr) << endl;    // true

用临时对象构造新的对象时,也会调用带右值引用参数的函数


unique_ptr<int> get_unique_ptr() {
    unique_ptr<int> tmp(new int);
    return tmp;
}

int main(){
    unique_ptr<int> ptr = get_unique_ptr(); // 调用带右值引用参数的拷贝构造函数,由tmp直接构造ptr
    return 0;
}

unique_ptr从名字就可以看出来,最终也是只能有一个智能指针引用资源,其他智能指针全部置空

三、带引用计数的智能指针


#include <iOStream>
#include <memory>
using namespace std;

template<typename T>
class RefCnt {
public:
    RefCnt(T* ptr = nullptr){
        mcount = (mptr == nullptr) ? 0 : 1;
    }

    void addRef() {
        mcount++;
    }
    int subRef() {
        return --mcount;
    }
private:
    int mcount;      // mptr指向某个资源的引用计数,线程不安全。使用atomic_int线程安全
    T* mptr;         //指向智能指针内部指向资源的指针,间接指向资源
};
template<typename T>
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr) :_ptr(ptr) {
        cout << "SmartPtr()" << endl;
        mpRefCnt = new RefCnt<int>(_ptr); // 用指向资源的指针初始化引用计数对象
    }
    ~SmartPtr() {
        if (0 == mpRefCnt->subRef()) {
            cout << "释放资源析构~SmartPtr()" << endl;
            delete _ptr;
        }
        else {
            cout << "空析构~SmartPtr()" << endl;
        }
    }
    const T& operator*() const { return *_ptr; }
    T& operator*() { return *_ptr; }
    const T operator->() const { return _ptr; }
    T operator->() { return _ptr; }
    SmartPtr(const SmartPtr<T>& src) :_ptr(src._ptr), mpRefCnt(src.mpRefCnt) {
        cout << "SmartPtr(const SmartPtr<T>& src)" << endl;
        if (_ptr != nullptr) {
            // 用于拷贝的对象已经引用了资源
            mpRefCnt->addRef();
        }
    }
    SmartPtr<T>& operator=(const SmartPtr<T>& src) {
        cout << "SmartPtr<T>& operator=" << endl;
        if (this == &src) {
            return *this;
        }
        // 当前智能指针指向和src相同的资源,考虑是否释放之前的资源
        if (0 == mpRefCnt->subRef()) {
            // 若之前指向的资源引用计数为1,释放之前的资源
            delete _ptr;
        }
        _ptr = src._ptr;
        mpRefCnt = src.mpRefCnt;
        mpRefCnt->addRef();
        return *this;
    }
private:
    T* _ptr;        // 指向资源的指针
    RefCnt<T>* mpRefCnt; // 指向该资源引用计数的指针 
};
int main(){
    SmartPtr<int> ptr1(new int);
    SmartPtr<int> ptr2(ptr1);
    SmartPtr<int> ptr3;
    ptr3 = ptr2;
    *ptr2 = 100;
    cout << *ptr2 << " " << *ptr3 << endl;
    return 0;
}

在这里插入图片描述

四、shared_ptr 和 weak_ptr

  • shared_ptr:强智能指针,可以改变资源的引用计数
  • weak_ptr:弱智能指针,不可改变资源的引用计数

weak_ptr -> shared_ptr -> 资源

智能指针的交叉引用问题


#include <iostream>
#include <memory>
using namespace std;
class B;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	shared_ptr<B> _ptrb;
};

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

int main() {
	shared_ptr<A> pa(new A());
	shared_ptr<B> pb(new B());

	pa->_ptrb = pb;
	pb->_ptra = pa;
	cout << pa.use_count() << endl;
	cout << pb.use_count() << endl;
	return 0;
}


在这里插入图片描述

解决办法: 定义对象时用shared_ptr,引用对象时用weak_ptr

 


class B;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	weak_ptr<B> _ptrb;
};

class B {
public:
	B() {
		cout << "B()" << endl;
	}
	~B() {
		cout << "~B()" << endl;
	}
	weak_ptr<A> _ptra;
};

weak_ptr只是用于观察资源,不能够使用资源,并没有实现operator*和operator->。可以使用weak_ptr对象的lock()方法返回shared_ptr对象,这个操作会增加资源的引用计数。

五、多线程访问共享对象的线程安全问题

线程A和线程B访问一个共享对象,如果线程A已经析构这个对象

线程B又要调用该共享对象的成员方法,线程B再去访问该对象,就会发生不可预期的错误


#include <iostream>
#include <memory>
#include <thread>
using namespace std;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	void test() {
		cout << "test()" << endl;
	}
};

void handler01(A* q) {
	// 睡眠2s,使得主线程进行delete
	std::this_thread::sleep_for(std::chrono::seconds(2));
	q->test();
}
int main() {
	A* p = new A();
	thread t1(handler01, p);
	delete p;
	t1.join();
	return 0;
}

在这里插入图片描述

在多线程访问共享对象的时候,往往需要lock检测一下对象是否存在。

开启一个新线程,并传入共享对象的弱智能指针。


#include <iostream>
#include <memory>
#include <thread>
using namespace std;
class A {
public:
	A() {
		cout << "A()" << endl;
	}
	~A() {
		cout << "~A()" << endl;
	}
	void test() {
		cout << "test()" << endl;
	}
};

void handler01(weak_ptr<A> pw) {
	shared_ptr<A> ps = pw.lock();
	// lock方法判定资源对象是否析构
	if (ps != nullptr) {
		ps->test();
	}else {
		cout << "A对象已经析构,无法访问" << endl;
	}
}

int main() {
	{
		shared_ptr<A> p(new A());
		//开启一个新线程,并传入共享对象的弱智能指针
		thread t1(handler01, weak_ptr<A>(p));
		// 将子线程和主线程的关联分离,也就是说detach()后子线程在后台独立继续运行,
		// 主线程无法再取得子线程的控制权,即使主线程结束,子线程未执行也不会结束。
		t1.detach();
	}
	// 让主线程等待,给时间子线程执行,否则main函数最后会调用exit方法结束进程
	std::this_thread::sleep_for(std::chrono::seconds(2));
	
	return 0;
}

在这里插入图片描述

六、自定义删除器

通常我们使用智能指针管理的资源是堆内存,当智能指针出作用域的时候,

在其析构函数中会delete释放堆内存资源,但是除了堆内存资源,智能指针还可以管理其它资源,

比如打开的文件,此时对于文件指针的关闭,就不能用delete了,

这时我们需要自定义智能指针释放资源的方法。


template<typename T> 
class ArrDeletor {
public:
	// 对象删除的时候需要调用对应删除器的()重载函数
	void operator()(T* ptr) const {
		cout << "ArrDeletor::operator()" << endl;
		delete[] ptr;
	}
};
template<typename T>
class FileDeletor {
public:
	// 对象删除的时候需要调用对应删除器的()重载函数
	void operator()(T* fp) const {
		cout << "FileDeletor::operator()" << endl;
		fclose(fp);
	}
};
int main() {
	unique_ptr<int, ArrDeletor<int>> ptr1(new int[100]);
	unique_ptr<FILE, FileDeletor<FILE>> ptr2(fopen("1.cpp", "w"));
	
	// 使用lambda表达式
	// function<返回值(参数)> 
	// []叫做捕获说明符,表示一个lambda表达式的开始。接下来是参数列表,即这个匿名的lambda函数的参数
	unique_ptr<int, function<void(int*)>> ptr1(
		new int[100],
		[](int* p)->void {
			cout << "call lambda release new int[]" << endl;
			delete[] p;
		}
	);
	unique_ptr<FILE, function<void(FILE*)>>  ptr2(
		fopen("1.cpp", "w"),
		[](FILE* p)->void {
			cout << "call lambda release fopen(\"1.cpp\", \"w\")" << endl;
			fclose(p);
		}
	);
	return 0;
}

到此这篇关于c++智能指针教程的文章就介绍到这了,更多相关C++智能指针内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 融会贯通C++智能指针教程

本文链接: https://lsjlt.com/news/133976.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 融会贯通C++智能指针教程
    目录一、基础知识介绍裸指针常出现以下几个问题:二、不带引用计数的智能指针不带引用计数的智能指针主要包括(1)auto_ptr源码(2)scoped_ptr(3)unique_ptr源...
    99+
    2024-04-02
  • C++智能指针shared_ptr
    目录1、什么是shared_ptr?2、shared_ptr支持哪些操作?3、如何创建shared_ptr的实例?4、什么是shared_ptr的引用计数?如何查看?5、shared...
    99+
    2024-04-02
  • C++SmartPointer智能指针详解
    目录一、为啥使用智能指针呢二、shared_ptr智能指针三、unique_ptr智能指针四、weak_ptr智能指针五、智能指针怎么解决交叉引用,造成的内存泄漏5.1 交叉引用的栗...
    99+
    2024-04-02
  • 【C++】智能指针(RAII)详解
        我们在上篇文章中(异常处理详解)提到了 RAII 。那么本篇文章会对此进行详解。重点是智能指针的详解。其中会讲解到 RAII 思想、auto_ptr、unique_ptr、shared_ptr、weak_ptr、循环引用问题。希...
    99+
    2023-09-06
    c++ 开发语言
  • C++智能指针之shared_ptr详解
    目录共享指针的初始化方式常用成员函数shared_ptr内存模型make_shared的优缺点优点缺点引用计数比较运算符总结共享指针的初始化方式 1.裸指针直接初始化,但不能通过隐式...
    99+
    2024-04-02
  • C++ 智能指针代码解析
    目录前言1,aoto_ptr2,unique_ptr3,share_ptr4, weak_ptr总结前言 如果在程序中使用new从堆分配内存,等到不再需要时,应使用delete将其释...
    99+
    2024-04-02
  • C++中Boost的智能指针scoped_ptr
    boost::scoped_ptr和std::auto_ptr非常类似,是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放。下列代码演示了该指针的基本应用: #includ...
    99+
    2024-04-02
  • C++中Boost的智能指针shared_ptr
    boost::scoped_ptr虽然简单易用,但它不能共享所有权的特性却大大限制了其使用范围,而boost::shared_ptr可以解决这一局限。顾名思义,boost::shar...
    99+
    2024-04-02
  • C++中Boost的智能指针weak_ptr
    循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。一个简单的例子如下: #include <string> #includ...
    99+
    2024-04-02
  • c++智能指针unique_ptr的使用
    目录1.为什么需要unique_ptr2.什么是unique_ptr3.unique_ptr特性4.如何使用unique_ptr4.1简单使用4.2指向数组5.unique_ptr需...
    99+
    2024-04-02
  • c++智能指针怎么调用
    在 C++ 中,智能指针通常有以下几种使用方式: 使用 std::shared_ptr: #include <memory...
    99+
    2024-02-29
    c++
  • C++ 智能指针:让指针更加智能,摆脱内存管理的烦恼
    智能指针:c++++ 中的特殊指针,封装原始指针,提供额外的功能。类型:std::unique_ptr:唯一所有权,指针失效后释放内存。std::shared_ptr:共享所有权,最后一...
    99+
    2024-05-09
    c++ 智能指针
  • C语言 智能指针 shared_ptr 和 weak_ptr
    weak_ptr引入可以解决shared_ptr交叉引用时无法释放资源的问题。 示例代码: #include <iostream> #include <memory...
    99+
    2024-04-02
  • C++超详细讲解智能指针
    目录一、内存泄漏-永恒的话题二、深度思考三、智能指针分析四、小结一、内存泄漏-永恒的话题 动态申请堆空间,用完后不归还C++ 语言中没有垃圾回收的机制指针无法控制所指堆空间的生命周期...
    99+
    2024-04-02
  • C++中怎么使用智能指针
    这篇文章给大家介绍C++中怎么使用智能指针,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、为什么要使用智能指针一句话带过:智能指针就是帮我们C++程序员管理动态分配的内存的,它会帮助我们自动释放new出来的内存,从而...
    99+
    2023-06-20
  • C++中如何使用智能指针
    这期内容当中小编将会给大家带来有关C++中如何使用智能指针,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。内存泄露是C++程序员都头疼的大问题。C++缺乏像JAVA、C#一样,拥有GC这么一项有利的武器,它...
    99+
    2023-06-17
  • C++Smart Pointer智能指针怎么用
    这篇文章主要讲解了“C++Smart Pointer智能指针怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++Smart Pointer智能指针怎么用”吧!一、为...
    99+
    2023-06-29
  • C++智能指针shared_ptr怎么使用
    本篇内容介绍了“C++智能指针shared_ptr怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、什么是shared_ptr?C+...
    99+
    2023-06-29
  • C/C++中智能指针的用法详解
    目录前言一、什么是智能指针二、使用方法1.shared_ptr2.unique_ptr3.weak_ptr前言 本章主要介绍一些C/C++中智能指针的实现原理以及如何使用 一、什么是...
    99+
    2023-01-04
    C++智能指针使用 C++智能指针
  • C++11 智能指针的具体使用
    目录智能指针的原理RAII智能指针的原理auto_ptr1.auto_ptr的使用及问题unique_ptrshared_ptrshared_ptr的循环引用智能指针的原理 RAII...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作