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就是创建所需修改的一个副本,对副本进行修改,当副本成功的完成了这些操作以后,再交换两者。