C++ Exception

void fun(SimpleClass*)

在DEBUG中使用Assert可以 用#define NDBEUG 在release版本中关掉这个功能


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

class MyError : public runtime_error
{
	public: 
		MyError(const string& msg = " "):runtime_error(msg){}
};
int main()
{
	try
	{
		throw MyError("This is my error");
	}
	catch(MeError& x)
	{
		cout << x.what() <<endl;
	}
}

这里runtime_error 是继承自exception 这个基类 包含于std的命名空间中。 

在Myerror的构造函数中对继承下来的msg成员变量进行赋值,然后就能在抛出异常的时候用使用what()成员函数取得赋入的信息。

同样的还有另外一个logic_error继承自exception。 

logic_error :    domain_error invalid_argumentlength_error out_of_range  bad_Cast bad_typeid

runtime_error: range_erroroverflow_error bad_alloc


2.异常匹配

class Except1
{};
class Except2
{
public:
	Except2(const Except1&)
	{}
};

void f()
{
	throw Except1();
}

int main()
{
	try 
	{
		f();
	} 
	catch(Except2&)
	{
		cout << 2<<endl;
		getchar();
	}
	catch(Except1&)
	{
		cout << 1<<endl;
		getchar();
	}

}
这里无论是不是使用引用都会匹配到Except1 而不会转型到Except2

只有子类可以转型到基类的异常进行匹配, 这也是我们使用引用来抛出异常的另外一个原因,我们并不希望是由于基类而捕获的异常而把子类对象的给剪裁了。


如果想捕获所有异常则

catch(...)
{
    //do something
}

这样就能捕获所有类型的异常


如果一个被抛出的异常在任何一个层次都没有能够被捕获,那么就胡调用一个在头文件<exception>中定义的函数 terminate(),而在这个函数中一个abort(); 这样就会使程序当掉。

我们可以使用set_terminate()这个函数来做一些处理 ,这个函数接受一个形如 void newTerminate() 这样一个函数。 

他返回一个函数指针,指向原来那个老的terminate函数,这样我们就能恢复到原来的版本。


在另外两个情况下terminate()会被调用

1。局部对象的析构函数抛出异常,此时栈正在做反解工作,异常的抛出过程被打断。则会terminate。

2. 全局对象或者静态对象的构造函数或者析构函数抛出一个异常。


3.清理

在构造函数中如果有一个异常被抛出那么析构函数是不会被调用的,这个时候我们需要用到一种思想

RAII(Resource Acquisition Is Initialization) 核心思想是使用栈的反解,把class B中的一个资源包裹成一个对象A ,在对象A中对这个资源进行内存分配等本来需要在B的构造函数中进行的工作。那么实际上A就是B中的一个局部对象。当在A中的构造中发生错误的时候,由于是局部对象,所以他在栈反解的时候就会因为脱离在B中的生命周期而被释放。


class B

{

private ;

int* a;

int* a2;

public :

a = new int[10];

a2 = new int[20];

}


class B
{
private :
		int* a;
		int* a2;
public :
		B()
		{
			a = new int[10];
			//May occur error
			a2 = new int[20];
		}
}

这里在a2就可能发生了异常,而导致分配失败,但是此时a的内存又已经被分配了,这样就发生了内存泄漏。

#include <iostream>
#include <cstddef>

template<typename T , int sz = 1>
class myWrap
{
private:
	T* ptr;
public:
	class RangeError
	{};

	myWrap()
	{
		ptr = new T[sz];
		cout << "myWrap constructor" <<endl;
	}

	~myWrap()
	{
		delete[] ptr;
		cout << "myWrap destructor" <<endl;
	}

	T& operator[](int i) throw(RangeError)
	{
		if(i >= 0 && i < sz) return ptr[i];
		throw RangeError();
	}

};

class Cat
{
public:
	Cat()
	{
		cout << "CAT()" <<endl;
		//throw 47;
	}

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

class Dog
{
public:
	void* operator new[](size_t p)
	{
		cout << "Allocating a dog"<<endl;
		//return :: new Dog[p];
		throw 47;
	}
	void operator delete[](void *p)
	{
		cout <<"Deallocating a dog" <<endl;
		::delete[](p);
	}
};

class UseResources
{

	myWrap<Cat,3> cats;
		myWrap<Dog,1> dog;
public:
	UseResources()
	{ cout << "UseResources()" << endl ;}
	~UseResources()
	{ cout << "~UseResources()" << endl ;}
};

int main()
{
	try{
		UseResources ur;
	}
	catch(int)
	{
		cout << "inside int"<< endl;
		getchar();
	}
	catch(...)
	{
		cout << "outside int" << endl;
		getchar();
	}
}

这是一个典型的RAII,你可以发觉当Dog分配资源失败了以后CAT也被释放了。


auto_prt就是一个典型的RAII的实现 他在头文件<memory>中定义。

std::tr1::shared_prt也是一个RAII class他和auto_ptr的区别在于 auot_ptr无法专业所有权,一旦专业则auto_ptr原先只想的指针则会所有权。

而shared_ptr是以一个计数器实现的,当每次有一个对象产生时计数器+1,当这个对象离开了他的作用域以后计数器-1 ,当计数器为0的时候,则释放资源。shared_ptr可以通过指定所谓的"deleter"来指定释放动作。

std::tr1::shared_ptr<SimpleClass> pr1(new SimpleClass) 

类似于这样的把new内置于pr1中则有发生内存泄露的可能 ,如果当pr1的构造函数在调用中发生了异常,则先执行的new SimpleClass就无法回收。

所以把new SimpleClass作为单独的一句语句是一个更好的主意。

另外有些函数形如

void fun(SimpleClass*)
这样的函数会需要一个SimpleClass的指针对象,但是如果你传入一个被shared_ptr包裹了的对象就会有error这时候可以使用shared_ptr内置的函数get()来取得被包裹对象的指针。


weak_prt:

weak_ptr 是Boost智能指针拼图的最后一块。weak_ptr 概念是shared_ptr的一个重要伙伴。它允许我们打破递归的依赖关系。它还处理了关于悬空指针的一个常见问题。在共享一个资源时,它常用于那些不参与生存期管理的资源用户。这种情况不能使用裸指针,因为在最后一个 shared_ptr 被销毁时,它会释放掉共享的资源。如果使用裸指针来引用资源,将无法知道资源是否仍然存在。如果资源已经不存在,访问它将会引起灾难。通过使用 weak_ptr, 关于共享资源已被销毁的信息会传播给所有旁观的weak_ptrs,这意味着不会发生无意间访问到无效指针的情形。这就象是观察员模式(Observer pattern)的一个特例;当资源被销毁,所有表示对此感兴趣的都会被通知到。

 

对于以下情形使用 weak_ptr :

要打破递归的依赖关系

使用一个共享的资源而不需要共享所有权

避免悬空的指针

以上节选自互联网

weak_ptr需要一个weak_ptr类型的对象或者 shared_prt类型的对象作为参数来构造。 他并没有重载* 以及 ->操作符

然后他可以使用weak_ptr::lock()函数来返回他所观察的shared_ptr

std::shared_ptr<int> p1(new int(5));
std::weak_ptr<int> wp1 = p1; //p1 owns the memory.
 
{
  std::shared_ptr<int> p2 = wp1.lock(); //Now p1 and p2 own the memory.
  if(p2) //Always check to see if the memory still exists
  { 
    //Do something with p2
  }
} //p2 is destroyed. Memory is owned by p1.
 
p1.reset(); //Memory is deleted.
 
std::shared_ptr<int> p3 = wp1.lock(); //Memory is gone, so we get an empty shared_ptr.
if(p3)
{
  //Will not execute this.
}



4.异常规格说明。

形如在函数名 void f() throw(MyException , MyException2)

这样的叫做异常规格说明

void f() 表示可能抛出任何异常

void f() throw()表示不抛出任何异常。

如果抛出了一个不再规格说明中的异常那么就有一个unexpected函数会被调用。 而它会调用terminate()函数

同样你可以使用set_unexpected()来自定义

正因为这个原因所以使用异常规格说明的时候要注意,因为可能不只有你能看见的异常被抛出,有其他的抛出的异常而不被你预测到的话就可能会导致程序DOWN了

异常规格的说明也是signature的一种,子类也必须满足异常规格的说明才能服从is-a的关系,但是这取决于编译器有些编译去只是会发出警告,而有些则是error


5.Copy and Swap

异常安全有两个条件

1。不发生内存泄露

2.不允许数据被破坏

这样的保证可以使用copy and swap的思想来实现。

但是这并不是绝对的事情。

所谓copy and swap就是创建所需修改的一个副本,对副本进行修改,当副本成功的完成了这些操作以后,再交换两者。


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页