难度:
您的代码中有Exception吗?作为C++中最具争议的东西,就连在使用上也不是一件容易的事,您怎么看待异常呢?
如果想判断一个对象是否构造成功,我们可以用以下的三种方法
1、
struct A
{
A(int& i)
{
//其他代码
if(构造失败)
i = 0;
else
i = 1;
}
};
int i;
A a(i);
if(i==0)
cout<<”构造失败”<<endl;
2、
class A
{
public:
A()
{
//其他代码
if(构造失败)
isok_ = false;
else
isok_ = true;
}
bool isok() const
{ return isok_; }
private:
bool isok_;
};
A a;
if(!a.isok())
cout<<”构造失败”<<endl;
3、
class my_exception: public std::exception{};
struct A
{
A()
{
//其他代码
if(构造失败)
throw my_exception();
}
};
try
{
A a;
}
catch(my_exception& ex)
{ cout<<”构造失败”<<endl; }
综观三种方法,我们来做个简单的分析。
第一种,该构造函数提供了一个用于返回错误的标志变量,虽然可以判断是否构造成功,但是这个多余的变量更像是一个累赘。
第二种,构造函数上没有歧义了。但是它并没有降低复杂度。更重要的是,它和第一种方法有个通病,那就是析构函数会被正常调用。换句话说,这样的对象构造失败并不是语言所支持的,而是程序员自己的逻辑规定。
第三种,这就完全没有上面两种方法的问题,在构造函数中抛出异常,就意味这这个对象未构造成功,这是被语言所支持的,这样一来,class A的析构函数将不会作用于对象a。这一特性可以让我们不再为这类安全性操心了。
在某些函数中,我们可以通过按值返回错误码,但在某些情况下这类方法并不顶用,这样我们就可以考虑异常。
从上面的表现来看,并没有体现出异常带来复杂度。但是,当遇到资源管理时,其中就有很多事情也许会被我们忽略。
void test()
{
int* p= new int;
//其他代码
delete p;
}
如果中间的代码抛出异常,最好的情况就是内存泄露及带来不安全因素。我们应该加入异常处理
void test()
{
int *p =NULL;
try
{
p = new int;
//其他代码
}
catch(...)
{
delete p;
throw; //异常中立,保证了前一版本test函数的行为
}
delete p;
}
但这也许并不算最爽的解决方案。我们可以利用RAII技巧。来简化这个操作
template<typename T>
class auto_new
{
public:
auto_new():ptr_(NULL)
{
try
{
ptr_ = new T;
}
catch(std::bad_alloc)
{
//异常处理
}
}
~auto_new()
{
delete ptr_;
}
operator T*()
{ return ptr_; }
private:
T* prt_;
}
void test()
{
auto_new<int> p;
//其他的代码
}
这样就不用担心异常发生时带来的资源回收问题。当然,对于简单的资源,我们可以采用auto_ptr<>。
也许各位看官会认为,这样的做法也并没有降低复杂度,似乎反而增大了工作量。是的,但是这样的代码可以使我们更放心。
异常也不是十全十美的,它自身也存在很多的缺陷,比如它的运行成本比较高,如果正常的控制结构可以处理错误,那么就不应该去使用异常。异常的一个作用就是当某个部分出现异常状况,那么我们可以通过异常来通知另一个部分。例如,当程序出现异常,那么我们可以把这个异常层层往上传递到函数的调用点,而其他的错误处理方式并不这么方便。虽然异常可以在两个部分进行传递,但是它并不是跨线程的,我们不能在两个线程间传递异常。例如下面的代码就是错误的。
DWORD CALLBACK threadfunc(void*)
{
//其他代码
throw int(); //抛出异常
}
int main(){
try
{
DWORD tid;
HANDLE hdl = CreateThread(NULL, 0, &threadfunc, NULL, 0, &tid);
Sleep(500);
CloseHandle(hdl);
}
catch(...)
{ cout<<"catched"<<endl;}
}
threadfunc抛出的异常我们根本无法接收到。这样也说明了一个问题,当我们不确定线程函数中的代码是否会抛出异常的时候,我们都必须在其中加入try 块,以保证异常安全。例如上面的代码就应该写成下面这个样
DWORD CALLBACK threadfunc(void*)
{
try
{
//其他代码 //不确定这里是否会抛出异常
}
catch(...)
{}
}
int main(){
DWORD tid;
HANDLE hdl = CreateThread(NULL, 0, &threadfunc, NULL, 0, &tid);
Sleep(500);
CloseHandle(hdl);
}
//The End