CSDN博客

img Jinhao

混沌 IN C++::Exception思维

发表于2004/7/11 21:20:00  2974人阅读

难度:

您的代码中有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

0 0

相关博文

我的热门文章

img
取 消
img