构造函数和析构函数中的异常

构造函数和析构函数中的异常
2008-11-29 21:39
构造函数中的异常 在构造函数中抛出异常将中止对象的构造,这将产生一个没有被完整构造的对象。

对于C++来说,这种不完整的对象将被视为尚未完成创建动作而不被认可,也意味着其析构函数永远不会被调用。这个行为本身无可非议,就好像公安局不会为一个被流产的婴儿发户口然后再开个死亡证明书一样。但有时也会产生一些问题,例如:

class CSample
{
// ...

char
* m_pc;
};

CSample::CSample()
{
m_pc = new char[256];
// ...
throw -1; // m_pc将永远不会被释放
}

CSample::~CSample() // 析构函数不会被调用
{
delete[] m_pc;
}

解决这个问题的方法是在抛出异常以前释放任何已被申请的资源。一种更好的方法是使用“资源申请即初始化”的类型(如:句柄类、灵巧指针类等等)来代替一般的资源申请与释放方式,如:

templete <class T>
struct CAutoArray
{
CAutoArray(T* p = NULL) : m_p(p) {};
~CAutoArray() {delete[] m_p;}
T* operator=(IN T* rhs)
{
if (rhs == m_p)
return m_p;
delete[] m_p;
m_p = rhs;
return m_p;
}

// ...

T
* m_p;
};

class CSample
{
// ...

CAutoArray
<char> m_hc;
};


CSample::CSample()
{
m_hc = new char[256];
// ...
throw -1; // 由于m_hc已经成功构造,m_hc.~CAutoPtr()将会
// 被调用,所以申请的内存将被释放

}

注意:上述CAutoArray类仅用于示范,对于所有权语义的通用自动指针,应该使用C++标准库中的 "auto_ptr" 模板类。对于支持引用计数和自定义销毁策略的通用句柄类,可以使用白杨工具库中的 "CHandle" 模板类。

析构函数中的异常 析构函数中的异常可能在2种情况下被抛出:
  1. 对象被正常析构时
  2. 在一个异常被抛出后的退栈过程中——异常处理机制退出一个作用域,其中所有对象的析构函数都将被调用。

由于C++不支持异常的异常,上述第二种情况将导致一个致命错误,并使程序中止执行。例如:

class CSample
{
~
CSample();
// ...
};

CSample::
~CSample()
{
// ...
throw -1; // 在 "throw false" 的过程中再次抛出异常
}

void
Function(
void)
{
CSample iTest;
throw false; // 错误,iTest.~CSample()中也会抛出异常
}


如果必须要在析构函数中抛出异常,则应该在异常抛出前用 "std::uncaught_exception()" 事先判断当前是否存在已被抛出但尚未捕获的异常。例如:

class CSample
{
~
CSample();
// ...
};

CSample::
~CSample()
{
// ...
if (!std::uncaught_exception()) // 没有尚未捕获的异常
{
throw -1; // 抛出异常
}
}

void
Function(
void)
{
CSample iTest;
throw false; // 可以,iTest.~CSample()不会抛出异常
}

异常的组织 异常类型应该以继承的方式组织成一个层次结构,这将使以不同粒度分类处理错误成为可能。

通常,某个软件生产组织的所有异常都从一个公共的基类派生出来。而每个类的异常则从该类所属模块的公共异常基类中派生。

异常捕获和重新抛出
  • 异常捕获器的书写顺序应当由特殊到一般(先子类后基类),最后才是处理所有异常的捕获器("catch(...)")。否则将使某些异常捕获器永远不会被执行。
  • 为避免捕获到的异常被截断,异常捕获器中的参数类型应当为常引用型或指针型。
  • 在某级异常捕获器中无法被彻底处理的错误可以被重新抛出。重新抛出采用一个不带运算对象的 "throw" 语句。重新抛出的对象就是刚刚被抛出的那个异常,而不是处理器捕获到的(有可能被截断的)异常。

例如:

try
{
// ...
}
// 公钥加密错误

catch (const CPubKeyCipher::Exp& err)
{
if (可以恢复)
{
// 恢复错误

}
else
{
// 完成能做到的事情
throw; // 抛出
}

}
// 处理其它加密库错误
catch (const CryptoExp& err)
{
// ...
}
// 处理其它本公司模块抛出的错误
catch (const CompanyExp& err)
{
// ...
}

// 处理 dynamic_cast 错误
catch (const bad_cast& err)
{
// ...
}

// 处理其它标准库错误
catch (const exception& err)
{
// ...
}

// 处理所有其它错误
catch (...)
{
// ...
throw; // 抛出
}

异常和效率 对于绝大部分现代编译器来说,在不抛出异常的情况下,异常处理的实现在运行时不会有任何额外开销,也就是说:正常情况下,异常机制比传统的通过返回值判断错误的开销还来得 稍微小些。

相对于函数返回和调用的开销来讲,异常抛出和捕获的开销通常会大一些。不过错误处理代码通常不会频繁调用,所以错误处理时开销稍大一点基本上不是什么问题。这也是我们提倡仅将异常用于错误处理的原因之一。

http://hi.baidu.com/%B0%AE%D0%C4%CD%AC%C3%CB_%B3%C2%F6%CE/blog/item/ba1a7b1b36cfbb1e8618bfcb.html

原文地址:https://www.cnblogs.com/cute/p/2177575.html