c++异常+智能指针

索引

  • 异常
      • (1).传统异常
      • (2).异常概念
      • (3).异常的抛出和匹配规则
      • (4).异常的匹配规则
      • (5).异常的重新抛出
      • (6).异常安全
      • (7).异常规范
      • (8).继承异常
    • (9).异常的优缺点
  • 智能指针
    • (1).智能指针概念
      • (2).智能指针的实现
      • (3).智能指针的浅拷贝
    • (4).unique_ptr/shared_ptr
      • a.unique_ptr
      • b.shared_ptr
        • shared_ptr循环引用![在这里插入图片描述](https://img-blog.csdnimg.cn/3c4e616cd86f40b68d5d89f234e54f4f.png)
      • (5).定值删除器

异常

(1).传统异常

传统的错误处理机制

  • 1.终止程序

    如assert,缺陷:如果发生内存错误,除0错误就直接终止程序

  • 2.返回错误码
    缺陷:需要程序员自己去查找对应的错误,如系统的很多库的接口函数都是通过把错误码放到errno中表示错误。
    错误码不够清晰,不同程序就得定义一套错误码

c++针对上述的不足,引入异常,即使程序出现错误,但只要正确捕获,就不会影响错误之后的程序运行

double test(double a, double b)
{
	if (b == 0)
		throw"除0错误";
	else
		return  a / b;
}

int main()
{
	while (1)
	{
		try
		{
			double d1, d2;
			cin >> d1 >> d2;
			cout<<test(d1, d2)<<endl;
		}
		catch (const char* str)
		{
			cout << str << endl;
		}
	}
	


	return 0;
}

(2).异常概念

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

  1. throw:当问题出现时,程序会抛出一个异常
  2. catch:catch用于捕获异常,一个throw一定要有一个匹配的catch
  3. try:try块中的代码标识激活特定的异常,它通常后面跟着一个或多个catch块

(3).异常的抛出和匹配规则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码,即若throw"T" catch(T&str)两个T的类型必须是一样的 上述代码中如果将catch改成catch (const int str)会报错,因为throw的是一个字符串,那么捕获的也必须是字符串
  2. 选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
try
		{
			try
			{
				try
				{
					double d1, d2;
					cin >> d1 >> d2;
					cout << test(d1, d2) << endl;
				}
				catch (const char* str)
				{
					cout << str<<"一" << endl;

				}
				
			}
			catch (const char* s)
			{
				cout << s <<"二"<< endl;
			}
			
		}
		catch (const char* st)
		{
			cout << st << endl;
		}

此时与throw匹配的是cout << str<<"一" << endl;

  1. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个临时拷贝,这个拷贝的临时对象会在被catch以后销毁(处理类似于函数的传值返回)
  2. catch(…)可以捕获任意类型的异常。

有可能没有匹配的catch,此时会终止程序,而用catch(…)可以捕获任意类型的异常,使得程序可以继续运行,匹配的好处是可以知道是什么类型的错误,但这个catch(…)只知道报错,不知道是什么类型的错误还是很危险的,总比没有好。

  1. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获因为在派生类可以切片赋值给基类

(4).异常的匹配规则

  1. 首先检查throw本身是否在try快内部,在当前函数栈帧中寻找匹配的catch,找到了,直接跳到catch进行运行程序
  2. 没有在当前栈帧中匹配到,则退出当前栈帧,去上一层栈帧找,如果找到了,一样直接跳到与之匹配的catch
  3. 如果一直没有找到匹配的catch,直到main也没有找到catch,终止程序,所以一般在最后加一个catch(…)避免程序终止
  4. 找到匹配的catch时,throw与catch之间的句子不会执行(这有可能会出现内存泄露,智能指针解决),执行完catch后,其后面的句子继续执行。
    c++异常+智能指针_第1张图片

(5).异常的重新抛出

有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理
c++异常+智能指针_第2张图片

(6).异常安全

  • 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
  • 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
  • C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题,下面RAII解决上述问题

(7).异常规范

让使用者知道函数会抛出什么类型的异常

  • 函数后接throw()标识该函数不抛异常
  • 若无异常接口则可以抛任何类型异常
  • 在函数后throw(异常类型)该函数抛该类型异常
	// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
	void fun() throw(A,B,C,D);
	// 这里表示这个函数只会抛出bad_alloc的异常
	void* operator new (std::size_t size) throw (std::bad_alloc);
	// 这里表示这个函数不会抛出异常
	void* operator delete (std::size_t size, void* ptr) throw();
	// C++11 中新增的noexcept,表示不会抛异常
	thread() noexcept;
	thread(thread && x) noexcept;

(8).继承异常

不管是哪个派生类发生了异常,catch捕捉的时候是捕捉子类的异常

class People
{
public:
	People(const string& name, const string& sex, const string& age)
		:_name(name)
		,_sex(sex)
		,_age(age)
	{}
	virtual string what()const
	{
		string str = "PeopleException";
		str += _name;
		str += " ";
		str += _sex;
		str += " ";
		str += _age;
		return str;
	}

private:
	string _name;
	string _sex;
	string _age;
};
class student :public People
{
 public:
	student(const string&name, const string& sex, const string& age, const string&interest)
		:People(name,sex,age)
		,_sname(name+"stud")
		,_ssex(sex + "stud")
		,_sage(age + "stud")
		,_sinterest(interest)
	{}
	virtual string what()const
	{
		string str = "studentException";
		str += " ";
		str += _sname;
		str += " ";

		str += _ssex;
		str += " ";
		str += _sage;
		str += " ";

		str += _sinterest;
		return str;
	}

private:
	string _sname;
	string _ssex;
	string _sage;
	string _sinterest;

};
void test()
{
	if (rand() % 3 < 2)
	{
		throw student("zjt", "nan", "18", "computer");
	}
	else
	{
		cout << "success" << endl;
	}
}
int main()
{
	srand(time(0));

	while (1)
	{
		Sleep(1000);
		try
		{
			test();
		}
		catch (const People& e)
		{
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "unkonw Exception" << endl;
		}
	}
	return 0;
}

个人理解:有点想多态,throw的是子类的,catch基类,子类切片给基类,但此时是一种多态调用。
因此可以throw子类,catch基类

(9).异常的优缺点

优点

  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出各种信息,便于调式

  2. 错误码是层层返回,异常是直接返回
    缺点

  3. 标准库定义的不够好用,还想看堆栈等都不太支持,所以很多公司自己定义一套自己的,导致非常混乱

  4. c++没有垃圾回收器,所以申请内存和释放内存位置要十分小心,可能会导致内存泄露,需要RAII机制进行补充

  5. 异常有时会导致程序的执行流乱跳,并且非常混乱,并且是运行时出错抛异常就会乱跳 ,这会给我们跟踪调试增加困难。

总结
异常总体而言,利大于弊鼓励使用!

智能指针

(1).智能指针概念

double f()
{
	double a, b;
	cin >> a >> b;
	if (b == 0)
	{
		throw "除0错误";
	}
	else
		return a / b;
}
void Fun()
{
	int* p1 = new int;
	int* p2 = new int;
	cout << f() << endl;
	delete p1;
	delete p2;


}
int main()
{
	while (1)
	{
		try
		{
			Fun();
		}
		catch (const char* str)
		{
			cout << str << endl;
		}
		catch (...)
		{
			cout << "unknow Exception" << endl;
		}
	}
	return 0;

	
}

如上所示,如果此时f()函数发生异常,那么该程序申请的内存就无法delete,就会造成内存泄露,找到一种解决措施——智能指针。

RAII是指导思想:资源获得即初始化,是一种利用对象生命周期来控制程序资源(如内存,文件句柄,网络连接等)简单技术
在对象构造时获取资源,使得对资源的访问在对象生命周期内始终有效,最后在析构时释放资源。

将资源托管给一个对象

  1. 不需要显示的释放资源,不用担心类似上述的内存泄漏
  2. 采用这种方式,对象所需资源在其生命周期始终有效。

(2).智能指针的实现

智能指针是一种特殊的类,此时的类成员是一个指针。

template<class T>
class SmartPtr
{
public:
	SmartPtr(T*ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "~SmartPtr()" << endl;
		delete _ptr;
		_ptr = nullptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
int main()
{
	SmartPtr<int>ptr1(new int);
	SmartPtr<int>ptr2(new int);
	SmartPtr<pair<string, int>>ptr3(new pair<string, int>);
	return 0;
}

c++异常+智能指针_第3张图片
可以看到此时就算有与之前一样的异常也同样不会出现内存泄露这个问题。

(3).智能指针的浅拷贝

智能指针延申出来一个问题——浅拷贝,它所需要的是像原生指针一样的。
c++异常+智能指针_第4张图片
因为智能指针本意是为了托管资源,防止资源的内存泄露,此时分不清指向的结点时拷贝的还是原先指向的,会造成二次析构。为了解决这个问题,c++98出了一个auto_ptr

auto_ptr:管理权限转移,被拷贝的对象悬空,很多公司明确要求,不能使用auto_ptr

auto_ptr<int>ptr1(new int);
	auto_ptr<int>ptr2(new int);
	*ptr1 = 10;//此时没报错
	ptr2 = ptr1;
	//*ptr1 = 10;//此时就会报错,因为其已经悬空了
	cout << *ptr2 << endl;//此时输出的是10

所以auto_ptr其实根上述实现的smart_ptr差不多,只不过拷贝构造是

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

(4).unique_ptr/shared_ptr

c++11库才更新智能指针的实现,在这之前,boost(一个c++社区)搞出了更好用的scoped_ptr/shared_ptr/week_ptr,
c++11取其精华——unique_ptr/shared_ptr/weak_ptr

a.unique_ptr

unique_ptr实现原理:简单粗暴的防止拷贝,同时也防止赋值运算符重载
与我们自己实现唯一不同:
方法一:

private:
	Unique_Ptr(Unique_Ptr<T>& st);
	Unique_Ptr&operator=(Unique_Ptr<T>& st);


这是boost中scoped_ptr的实现方式,如果不声明为私有的话,可能有人会定义拷贝构造,或者运行时报错,但我们要的是编译就报错。
unique_ptr实现如下
直接将拷贝和赋值强制性疯掉。

unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

b.shared_ptr

原理:通过引用计数的方式实现多个ptr之间的资源共享,每个ptr内部,都维护一份计数,用来记录该资源被几个对象共享,对象被销毁时,对象的引用计数-1,如果引用计数时0,说明自己是最后一个使用该资源的对象,必须释放,如果不是0,那么还不能释放该资源,否则其他对象变成野指针。

template<class T>
class Shared_Ptr
{
public:

	void Release()
	{
		if (--(*_pcount) == 0 && _ptr)
		{
			cout << "Release()" << endl;
			delete _ptr;
			_ptr = nullptr;
			delete _pcount;
			_pcount = nullptr;

		}
	}
	Shared_Ptr(T* ptr)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}

	~Shared_Ptr()
	{
		cout << "~Shared_Ptr()" << endl;
		Release();
	}

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

	Shared_Ptr(const Shared_Ptr<T>& st)
		:_ptr(st._ptr)
		, _pcount(st._pcount)
	{
		(*_pcount)++;
	}
	T* get() const//获得指针
	{
		return _ptr;
	}
	int use_count()//返回计数个数
	{
		return *_pcount;
	}
	Shared_Ptr& operator=(const Shared_Ptr<T>& st)
	{
		//if(this!=&st)
		/*如果是按上述写的话,如果指向的是同一块空间还是要走一遍
			代码*/
		if (_ptr != st._ptr)
		{
			Release();
			_ptr = st._ptr;
			_pcount = st._pcount;
			(*_pcount)++;
		}
		return *this;
	}
private:
	
	T* _ptr;
	int* _pcount;
};
shared_ptr循环引用c++异常+智能指针_第5张图片

为了解决循环引用这个问题,c++引入了weak_ptr
实现原理:
weak_ptr拷贝构造shar_ptr,本质使weak_ptr指向shar_ptr,但是不参与计数管理。
通俗来讲就是,我不参与你的计数管理,但是我可以指向你的资源,资源的释放还是你自己的事情。

template<class T>
class Weak_Ptr
{
public:
	Weak_Ptr()//weak_ptr是无参构造
		:_ptr(nullptr)
	{}
	Weak_Ptr(const Shared_Ptr<T>&st )
		:_ptr(st.get())
	{}
	Weak_Ptr<T>& operator=(const Shared_Ptr<T>& st)
	{
		if (_ptr != st.get())
		{
			_ptr = st.get();
		}
		return *this;
	}
	

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

private:
	

	T* _ptr;
};


struct ListNode
{
	/*Shared_Ptr_next = nullptr;
	Shared_Ptr_prev = nullptr;*/
	Weak_Ptr<ListNode>_next;
	Weak_Ptr<ListNode>_prev;
	int _val = 0;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{

	Shared_Ptr<ListNode>p1(new ListNode);
	Shared_Ptr<ListNode>p2(new ListNode);
	cout << p1.use_count() << endl;
	cout << p2.use_count() << endl;
	p1->_next = p2;
	p2->_prev = p1;

(5).定值删除器

上述中我们删除直接都是delete,但是申请方式不一定就是new出来的单个指针

给不同申请方式的对象定义特定的仿函数,使得可以特定的删除叫做定制删除器

以unique_ptr模拟实现

template<class T>
struct default_delete
{
	void operator()(T* ptr)
	{
		delete ptr;
	}
};
template<class T, class D = default_delete<T>>
class Unique_Ptr
{
public:
	Unique_Ptr(T* ptr)
		:_ptr(ptr)
	{}

	~Unique_Ptr()
	{
		cout << "~Unique_Ptr()" << endl;
		/*delete _ptr;
		_ptr = nullptr;*/
		D del;
		del(_ptr);
		_ptr = nullptr;
	}
..........截取部分代码
此时
    Unique_Ptr<Date> up1(new Date);
	Unique_Ptr<Date, DeleteArray<Date>> up2(new Date[10]);
	Unique_Ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date)* 10));
	Unique_Ptr<FILE, Fclose> up4((FILE*)fopen("Test.cpp", "r"));

c++异常+智能指针_第6张图片

你可能感兴趣的:(C++语法分析,C++,c++,开发语言)