C++智能指针的使用

文章目录

智能指针的使用和原理

智能指针的使用场景

  1. 下面的程序中,new了以后,我们也delete了,但是因为抛异常导致后面的delete没有得到执行,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出。
    2.但是因为new本身也可能抛异常,连续的两个new和下面的Divide都可能会抛异常,让我们处理起来很麻烦。智能指针放到这样的场景里面就让问题简单多了。
cpp 复制代码
double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}

void Func()
{
	// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。
	// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
	// 但是如果array2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
	// 是智能指针,否则代码太戳了
	int* array1 = new int[10];
	int* array2 = new int[10];// 抛异常呢
	// 连续的两个new
	// 如果第一个new抛异常可以捕获
	// 如果第二个new抛异常,可以解决delete第二个new
	// 但是第一个new没有被delete
	// 这样就需要再第二个new的地方捕获异常,delete第一个new
	// 如果有很多个new呢,就太麻烦了,这里就需要智能指针了
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array1 << endl;
		cout << "delete []" << array2 << endl;
		delete[] array1;
		delete[] array2;
		throw; // 异常重新抛出,捕获到什么抛出什么
	}
	// ...
	cout << "delete []" << array1 << endl;
	delete[] array1;
	cout << "delete []" << array2 << endl;
	delete[] array2;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}

	return 0;
}

RAII和智能指针

  1. RAII是获取到资源立即初始化,它是一种管理资源的类的设计思想,本质是一种利用对象生命周期来管理获取到的动态资源,避免资源泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等。RAII在获取资源时把资源委托给一个对象 ,接着控制对资源的访问,资源在对象的生命周期内始终保持有效 ,最后在对象析构的时候释放资源,这样保障了资源的正常释放 ,避免资源泄漏问题。
  2. RAII像指针一样

在对象销毁后会正常的释放资源

cpp 复制代码
template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete[] " << _ptr << endl;
		delete[] _ptr;
	}

private:
	T* _ptr;
};

double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}

void Func()
{
	// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。
	// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
	// 但是如果array2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
	// 是智能指针,否则代码太戳了
	SmartPtr<int> p1 = new int[10];
	SmartPtr<int> p2 = new int[10];// 抛异常呢
	SmartPtr<int> p3 = new int[10];

	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}

	return 0;
}
  1. 智能指针类除了满足RAII的设计思路,还要方便资源的访问,所以智能指针类还像迭代器类一样
    重载 operator*/operator->/operator[] 等运算符,方便访问资源。
cpp 复制代码
template<class T>
class SmartPtr
{
public:
	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete[] " << _ptr << endl;
		delete[] _ptr;
	}

	// 重载运算符,模拟指针的⾏为,⽅便访问资源
	T& operator*()
	{
		return *_ptr;
	}

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

	T& operator[](size_t i)
	{
		return _ptr[i];
	}

private:
	T* _ptr;
};

void Func()
{
   p1[1] = 50;
   p4->first = 1;
   p4->second = 2;
   cout << p1[1] << endl;
}

C++标准库智能指针的使用

浅拷贝的问题,共同管理同一块资源,而深拷贝是两个指针管理不同的资源了,现在要求两个指针管理同一块资源?

cpp 复制代码
int main()
{
	// 需要p1和p2同时管理同一块资源,浅拷贝
	// 析构多次的问题如何解决?
	SmartPtr<int> p1 = new int[10];
	SmartPtr<int> p2(p1);

	return 0;
}

1.C++标准库中的智能指针都在< memory >这个头文件下,智能指针有好几种,除了weak_ptr他们都符合RAII和像指针一样访问的行为,原理上而言主要是解决智能指针拷贝时的思路不同
2. C++98的智能指针
1、auto_ptr是C++98时设计出来的智能指针,它的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象 ,这是⼀个非常糟糕的设计,因为他会把被拷贝对象悬空(相当于被拷贝对象是空指针了),访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使用auto_ptr

cpp 复制代码
struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

	// 本身不需要析构,为了验证auto_ptr是否可以解决析构两次的问题
	~Date()
	{
		cout << "~Date()" << endl;
	}
};

int main()
{
	// 拷⻉时,管理权限转移,被拷⻉对象ap1悬空
	auto_ptr<Date> ap1(new Date);

	auto_ptr<Date> ap2(ap1);
	// 空指针访问,ap1对象已经悬空
	// ap1->_year++;

	return 0;
}

C++11的智能指针
2、unique_ptr是唯一指针,他的特点是不支持拷贝,只支持移动。如果不需要拷贝的场景就非常建议使用它。把拷贝构造和拷贝赋值给(封)delete了

cpp 复制代码
int main()
{
   unique_ptr<Date> up1(new Date);

   不支持拷贝
   // unique_ptr<Date> up2(up1);

   支持移动,移动后up1也悬空了
   unique_ptr<Date> up3(move(up1));

   return 0;
}

3、shared_ptr共享指针,他的特点是支持拷贝,也支持移动 。如果需要拷贝的场景就需要使用他了 。底层是用引用计数的方式实现的。

cpp 复制代码
int main()
{
   shared_ptr<Date> p1(new Date);
   shared_ptr<Date> p2(p1);
   shared_ptr<Date> p3(p1);
   
   cout << p1.use_count() << endl;// 3
   p1->_day++;
   cout << p1->_day << endl; // 2
   cout << p2->_day << endl; // 2
   cout << p3->_day << endl; // 2
   
   return 0;
}

4、weak_ptr弱(辅助解决shared_ptr的一个问题)指针,它不同于上面的指针,它不支持RAII,也就意味着不能用它直接管理资源,weak_ptr的产生本质是要解决shared_ptr的一个循环引用导致内存泄漏的问题

相关推荐
Cinema KI3 小时前
吃透C++继承:不止是代码复用,更是面向对象设计的底层思维
c++
Dream it possible!5 小时前
LeetCode 面试经典 150_二叉搜索树_二叉搜索树中第 K 小的元素(86_230_C++_中等)
c++·leetcode·面试
Bona Sun7 小时前
单片机手搓掌上游戏机(十四)—pico运行fc模拟器之电路连接
c语言·c++·单片机·游戏机
oioihoii7 小时前
性能提升11.4%!C++ Vector的reserve()方法让我大吃一惊
开发语言·c++
小狗爱吃黄桃罐头8 小时前
《C++ Primer Plus》模板类 Template 课本实验
c++
码力码力我爱你10 小时前
Harmony OS C++实战
开发语言·c++
Vect__10 小时前
别再只懂 C++98!C++11 这7个核心特性,直接拉开你与普通开发者的差距
c++
想唱rap10 小时前
C++ map和set
linux·运维·服务器·开发语言·c++·算法
小欣加油11 小时前
leetcode 1018 可被5整除的二进制前缀
数据结构·c++·算法·leetcode·职场和发展
玖剹13 小时前
递归练习题(四)
c语言·数据结构·c++·算法·leetcode·深度优先·深度优先遍历