lesson6:异常和智能指针

1. C++异常概念  

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误

  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕获。
  • try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。

2. 异常的抛出和捕获

2.1 异常的抛出和匹配原则

#include <iostream>
#include <functional>
#include <map>
using namespace std;
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";
	else
		return ((double)a / (double)b);
}

void Func()
{
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	/*catch (int errid)
	{
		cout << errid << endl;
	}*/
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}

	cout << "Func() end" << endl;
}

int main()
{
	while (1)
	{
		try
		{
			Func();
		}
		catch (int errid)
		{
			cout << errid << endl;
		}
		catch (char errmsg)
		{
			cout << errmsg << endl;
		}
		/*catch (const char* errmsg)
		{
			cout << errmsg << endl;
		}*/
		catch (...) // 捕获任意类型的异常 -- 防止出现未捕获异常时,程序终止
		{
			cout << "未知异常" << endl;
		}
	}

	return 0;
}

 

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  2. 选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似 于函数的传值返回)
  4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。

2.2 将异常信息和id封装成一个类 

#include<iostream>
#include <string>
using namespace std;
class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _errid(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

	int GetErrid()
	{
		return _errid;
	}

protected:
	string _errmsg;
	int _errid;
};

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		Exception e("除0错误", 1);
		throw e;
	}
	else
	{
		return ((double)a / (double)b);
	}
}

void Func1()
{
	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl;
}

void Func2()
{

}

// 1、抛异常可以抛任意类型对象
// 2、捕获时,要求类型匹配
int main()
{
	while (1)
	{
		try
		{
			Func1();
		}
		catch (const Exception& e)
		{
			cout << e.what() << endl;
		}
		catch (...) // 捕获任意类型的异常 -- 防止出现未捕获异常时,程序终止
		{
			cout << "未知异常" << endl;
		}
	}

	return 0;
}

  1. 抛异常可以抛任意类型对象
  2. 捕获时,要求类型匹配

2.3 在函数调用链中异常栈展开匹配原则

栈展开过程如下:

 

  • 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句,如果有匹配的,则处理
  • 没有则退出当前函数栈,继续在调用函数的栈中进行查找,不断重复上述过程,若到达main函数的栈,依旧没有匹配的,则终止程序

  2.4 服务器开发中通常使用的异常继承体系

  •  在实际中抛出和捕获的匹配原则中,并不都是类型完全匹配,可以抛出派生类的对象,再使用基类捕获,
  •  因为继承关系,只需要捕捉基类的异常信息

  2.5 C++ 11异常处理的优点

  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包 含堆栈调用的信息,这样可以帮助更好的定位程序的bug
  2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那 么我们得层层返回错误,最外层才能拿到错误,
  3. 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们 也需要使用异常。
  4. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如 T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回 值表示错误。

2.6 C++11异常处理的缺点

  1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会 导致我们跟踪调试时以及分析程序时,比较困难。
  2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计
  3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常 安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
  4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱
  5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常 规范有两点:
    1. 抛出异常类型都继承自一个基类。
    2. 函数是否抛异常、抛什么异常,都 使用 func() throw();的方式规范化

3. 智能指针

3.1 案例

内存泄露是指因为疏忽或错误,造成程序未能释放已经不再使用的内存的情况。比如

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	int* ptr = new int;
	//...
	cout << div() << endl;
	//...
	delete ptr;
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}
  •  执行上述代码时,如果用户输入的除数为0,那么div函数中就会抛出异常,这时程序的执行流会直接跳转到主函数中的catch块中执行,最终导致func函数中申请的内存资源没有得到释放

利用异常的重新捕获解决 

对于这种情况,我们可以在func函数中先对div函数中抛出的异常进行捕获,捕获后先将之前申请的内存资源释放,然后再将异常重新抛出。比如:

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	int* ptr = new int;
	try
	{
		cout << div() << endl;
	}
	catch (...)
	{
		delete ptr;
		throw;
	}
	delete ptr;
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

 3.2 使用

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		cout << "delete: " << _ptr << endl;
		delete _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	SmartPtr<int> sp(new int);
	//...
	cout << div() << endl;
	//...
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}
  • 无论程序是正常执行完毕返回了,还是因为某些原因中途返回了,或是因为抛异常返回了,
  • 只要SmartPtr对象的生命周期结束就会调用其对应的析构函数,进而完成内存资源的释放 

 3.3 缺陷

对于当前实现的SmartPtr类,如果用一个SmartPtr对象来拷贝构造另一个SmartPtr对象,或是将一个SmartPtr对象赋值给另一个SmartPtr对象,都会导致程序崩溃。比如:

int main()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(sp1); //拷贝构造

	SmartPtr<int> sp3(new int);
	SmartPtr<int> sp4(new int);
	sp3 = sp4; //拷贝赋值
	
	return 0;
}
  • 编译器默认生成的拷贝构造函数对内置类型完成值拷贝(浅拷贝),因此用sp1拷贝构造sp2后,相当于这sp1和sp2管理了同一块内存空间,当sp1和sp2析构时就会导致这块空间被释放两次 

4. RAII思想

是一种利用对象生命周期来控制程序资源的简单技术->资源管理权的转移

就是将资源管理权交给别人,在对象构造时获取资源,对象析构的时候释放资源

#include <iostream>
#include <string>
#include <windows.h>
#include <time.h>
#include <memory>
using namespace std;
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

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

	T& operator*()
	{
		return *_ptr;
	}

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

private:
	T* _ptr;
};

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
	//private:
	int _a1 = 0;
	int _a2 = 0;
};

int main()
{
	auto_ptr<A> ap1(new A);
	ap1->_a1++;
	ap1->_a2++;

	auto_ptr<A> ap2(ap1);
	//ap1->_a1++;// 空指针的使用
	//ap1->_a2++;


	SmartPtr<A> sp1(new A);
	sp1->_a1++;
	sp1->_a2++;

	//SmartPtr<A> sp2(sp1);

	return 0;
}

 

 

  • sp1和sp2都是指向的同一资源RAll的核心就管理权的转移所以需要浅拷贝
  • C++98的时候 ,auto_ptr  资源管理权转移,不负责任的拷贝,会导致被拷贝对象悬空
  • 大多数时候的auto_ptr都是禁止使用的

4.1 模拟实现auto_ptr

拷贝构造 && 赋值重载的情况下会出现那个不负责任的拷贝 

#include <iostream>
#include <string>
using namespace std;
class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
//private:
	int _a1 = 0;
	int _a2 = 0;
};

namespace bit {
	// C++98  auto_ptr 管理权转移,被拷贝对象的出现悬空问题
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		// ap1 = ap2;
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 特殊情况: 自己跟自己赋值
			if (this != &ap)
			{
				// 如果_ptr之前有管理的资源,将它释放掉,再进行赋值
				if (_ptr)
				{
					cout << "Delete:" << _ptr << endl;
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}

			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

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

	private:
		T* _ptr;
	};

}

int main()
{
	bit::auto_ptr<A> ap1(new A);
	ap1->_a1++;
	ap1->_a2++;
	
	cout << ap1->_a1 << " : " << ap1->_a2 << endl;
	
	bit::auto_ptr<A> ap2(ap1);
	//ap1->_a1++;// 空指针的使用
	//ap1->_a2++;
	//cout << ap1->_a1 << " : " << ap1->_a2 << endl;
	
	cout << ap2->_a1 << " : " << ap2->_a2 << endl;
	return 0;
}

  •  C89的auto_ptr  是资源管理权转移,是不负责任的拷贝,会导致被拷贝对象悬空

4.2 模拟实现unique_ptr

 禁掉拷贝构造 && 赋值重载,就不会出现那个不负责任的拷贝了

 拷贝构造时->管理者+1

赋值重载时->清除以前的管理->管理者+1

#include <iostream>
#include <string>
using namespace std;
class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
//private:
	int _a1 = 0;
	int _a2 = 0;
};

namespace bit {
	template<class T>
	class unique_ptr
	{
	private:
		// 防拷贝 C++98 ,只声明不实现 
		//unique_ptr(unique_ptr<T>& ap);
		//unique_ptr<T>& operator=(unique_ptr<T>& ap);
	public:
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		// 防拷贝 C++11
		unique_ptr(unique_ptr<T>& ap) = delete;// 禁掉默认拷贝构造
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete; // 禁掉默认赋值重载

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

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

	private:
		T* _ptr;
	};

}

int main()
{
	bit::unique_ptr<A> ap1(new A);
	ap1->_a1++;
	ap1->_a2++;
	
	cout << ap1->_a1 << " : " << ap1->_a2 << endl;
	
	bit::unique_ptr<A> ap2(ap1);// error
	//ap1->_a1++;// 空指针的使用
	//ap1->_a2++;
	//cout << ap1->_a1 << " : " << ap1->_a2 << endl;
	
	//cout << ap2->_a1 << " : " << ap2->_a2 << endl;
	return 0;
}

  • C++11新增的 unique_ptr禁止智能指针的拷贝,但是没有解决根本问题

4.3 模拟实现shared_ptr 

共同管理一段空间,引用计数 记录管理者解决拷贝问题

#include <iostream>
#include <string>
using namespace std;
class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
//private:
	int _a1 = 0;
	int _a2 = 0;
};

namespace bit {
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(new int(1))
		{}

		void Release()
		{
			// 减减被赋值对象的计数,如果是最后一个对象,要释放资源
			if (--(*_pCount) == 0)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
				delete _pCount;
			}
		}

		~shared_ptr()
		{
			Release();
		}

		// sp1(sp2)
		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			(*_pCount)++;// 资源管理者加1个
		}

		// sp1 = sp5
		// sp1 = sp1
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			// 处理自己 = 自己的情况
			if (_ptr == sp._ptr)
			{
				return *this;
			}

			// 赋值,首先需要将自己管理的资源释放
			Release();

			// 共管新资源,++计数
			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

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

	private:
		T* _ptr;

		// 引用计数解决拷贝问题
		int* _pCount;
	};

}

int main()
{
	bit::shared_ptr<A> ap1(new A);
	ap1->_a1++;
	ap1->_a2++;
	
	cout << ap1->_a1 << " : " << ap1->_a2 << endl;
	
	bit::shared_ptr<A> ap2(ap1);
	ap1->_a1++;// 正常使用
	ap1->_a2++;
	cout << ap1->_a1 << " : " << ap1->_a2 << endl;
	cout << ap2->_a1 << " : " << ap2->_a2 << endl;
	return 0;
}

  • C++11新增的 shared_ptr解决了99%的智能指针的拷贝问题
  • 核心思路: 使用引用计数标记这个资源有多少个管理者(浅拷贝)
    • 这里不能使用静态计数对象,因为静态成员属于整个类,存放在静态区,只有一份

 4.4 shared_ptr导致的循环引用问题

#include <iostream>
#include <string>
using namespace std;

struct Node
{
	int _val;
	std::shared_ptr<Node> _next;
	std::shared_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node" << endl;
	}
};

int main()
{
	std::shared_ptr<Node> n1(new Node);
	std::shared_ptr<Node> n2(new Node);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
	return 0;
}

  •  这个循环引用就是shared_ptr不能解决的1%的情况
    • n1->_next = n2;
    • n2->_prev = n1;
       

 4.5 模拟实现weak_ptr 

为了解决shared_ptr循环引用的问题,是它的next和prev不增加计数 

#include <iostream>
using namespace std;

namespace bit {
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			, _pCount(new int(1))
		{}

		void Release()
		{
			// 减减被赋值对象的计数,如果是最后一个对象,要释放资源
			if (--(*_pCount) == 0)
			{
				cout << "Delete:" << _ptr << endl;
				delete _ptr;
				delete _pCount;
			}
		}

		~shared_ptr()
		{
			Release();
		}

		// sp1(sp2)
		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			(*_pCount)++;// 资源管理者加1个
		}

		// sp1 = sp5
		// sp1 = sp1
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			// 处理自己 = 自己的情况
			if (_ptr == sp._ptr)
			{
				return *this;
			}

			// 赋值,首先需要将自己管理的资源释放
			Release();

			// 共管新资源,++计数
			_ptr = sp._ptr;
			_pCount = sp._pCount;

			(*_pCount)++;

			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

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

		int use_count()
		{
			return *_pCount;
		}

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;

		// 引用计数解决拷贝问题
		int* _pCount;
	};

	// 辅助型智能指针,使命配合解决shared_ptr循环引用问题
	template<class T>
	class weak_ptr
	{
	public:
		// -----  构造函数  ------ 
		weak_ptr()
			:_ptr(nullptr)
		{}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}
		weak_ptr(const weak_ptr<T>& wp)
			:_ptr(wp._ptr)
		{}
		// -----  构造函数  ------ 
		
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

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

	
}

struct Node
{
	int _val;
	/*std::shared_ptr<Node> _next;
	std::shared_ptr<Node> _prev;*/

	bit::weak_ptr<Node> _next;
	bit::weak_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node" << endl;
	}
};

int main()
{
	bit::shared_ptr<Node> n1(new Node);
	bit::shared_ptr<Node> n2(new Node);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
	return 0;
}

  • 循环引用 -- weak_ptr不是常规智能指针,没有RAII,不支持直接管理资源
    • _next 和_prev是weak_ ptr时,它不参与资源释放管理,可以访问和修改到资源,
    • 但是不增加次数,不存在循环引用的问题,
  • weak_ptr主要用shared_ptr构造,用来解决shared_ptr循环引用问题 
  • weak_ptr就是为了解决shared_ptr循环引用问题产生的,它就是shared_ptr的一个小根班

 5. 定制删除器

#include <iostream>
#include <stdlib.h>
using namespace std;
// 仿函数: 重载()
template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "free" << ptr << endl;
		free(ptr);
	}
};

struct Node
{
	int _val;
	std::shared_ptr<Node> _next;
	std::shared_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node" << endl;
	}
};

// 定制删除器
void test_shared_ptr3()
{
	// shared_ptr传对象
	// 仿函数对象
	std::shared_ptr<Node> n1(new Node[5], DeleteArray<Node>());
	std::shared_ptr<Node> n2(new Node);
	std::shared_ptr<int> n3(new int[5], DeleteArray<int>());
	std::shared_ptr<int> n4((int*)malloc(sizeof(12)), Free<int>());

	// lambda
	std::shared_ptr<Node> n5(new Node[5], [](Node* ptr) {delete[] ptr; });
	std::shared_ptr<Node> n6(new Node);
	std::shared_ptr<int> n7(new int[5], [](int* ptr) {delete[] ptr; });
	std::shared_ptr<int> n8((int*)malloc(sizeof(12)), [](int* ptr) {free(ptr); });

	// unique_ptr传类型
	std::unique_ptr<Node, DeleteArray<Node>> up(new Node[5]);
}

int main()
{
	//std::shared_ptr<Node> n1(new Node[5]);// error
	test_shared_ptr3();
	return 0;
}

  •  定制删除器解决类型不匹配的的问题,delete ptr和delete ptr[]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值