1. 从std::exception派生异常类。除了一些非常罕见的情况,例如负担不了需函数的开销。把std::exception作为异常基类是合理的,当它被广泛使用后,将答应程序员捕捉任何异常而不必使用catch(...).更多关于catch(...)的内容,请看后文。
2. 使用虚拟继续。这个深刻的洞察力来自Andrew Koenig. 当抛出的一个异常是从多个基类派生,并且这些基类有共同的部分,catch点就会碰到歧义问题,从异常基类虚拟继续可以防止这种歧义问题:
#include <iostream>
strUCt my_exc1 : std::exception { char const* what() const throw(); };
struct my_exc2 : std::exception { char const* what() const throw(); };
struct your_exc3 : my_exc1, my_exc2 {};
int main()
{
try { throw your_exc3(); }
catch(std::exception const& e) {}
catch(...) { std::cout << "whoops!" << std::endl; }
}
上面的程序将打印出“whoops” ,因为C++运行时刻无法决定用那个exception实例去匹配第一个catch.(秃子:我的建议是这里最好别使用多重继续)
3. 不要内嵌std::string对象或者其他拷贝构造可能抛出异常的数据成员、基类。在上述点抛出异常将导致直接调用std::terminate().让基类或数据成员的默认构造函数可能抛出异常也是同样糟糕的主意,你本来是打算通过一个包含对象构造的throw表达式报告异常, 程序却无谓地中止了:
throw some_exception();
当发生异常拷贝时,有几种方法避免复制字符串对象,例如在异常对象中嵌入一个定长存储区,或者通过引用计数来治理字符串。不过,在采用这些方法前,先考虑考虑下一条。
4. 只在确实需要的时候才格式化what()返回的信息。格式化是一个典型的内存相关的操作,有可能抛出异常。最好把格式化推迟到栈展开之后,因为栈展开可能释放某些资源。对what()函数用catch(...)块加以保护是一个好主意,这样你就可以在格式化抛出异常时有了一个退路。
5. 不要太在意what()的信息。在异常抛出点,对程序员来说,这是给出错误信息的好机会,但是你未必能够把相关信息组合成用户可以理解的形式。国际化就一个典型的情况。Peter Dimov给出了良好建议:建一个错误信息格式化的表格,把what()的字符串作为这个表的键。当标准库抛出异常时,假如我们只能获得其标准的what()字符串……
6. 在异常类的public接口中暴露导致错误的有关信息。返回固定信息的what()意味着你忽视了暴露信息,而用户可能需要提供相关信息。例如,你的异常想报告数字范围错,报错的代码应该能够透过异常的公共接口让异常包含导致问题的那个变量值。假如你只是在what()中以文本方式表现这些数字,那些需要根据信息做更多(或更少)处理的程序员日子将很难过。
7. 假如可能,让你的异常类对两次析构免疫。几款流行的编译器偶然会使异常对象被销毁两次。假如你能采取措施防御危害(比如,把释放的指针置零)就可以使代码更健壮。