关于函数可能抛出的异常的一些声明
void f() throw() 表示f不允许抛出任何异常,即f是异常安全的。
void f() throw(...) 表示f可以抛出任何形式的异常。
void f() throw(exceptionType); 表示f只能抛出exceptionType类型的异常。
声明一个不抛出异常的函数后,你有责任保证在你的函数的实现里面不会抛出异常。
附上别人的一些心得,有空可以验证一下:
1) 函数后面声明 throw() 只是接口的提供者和接口的使用者间的默契或称协议。
2) 这种协议不影响正常的异常处理流程。
3) vc2005 会在编译器对这种协议的遵守情况进行检查,并在为遵守协议的情况下 给出警告。
用于打印调用堆栈的辅助函数,可以在异常发生的时候打印事故现场 :(需要编译时加上-g -rdynamic两个选项)
std::string getStacktrace()
{
int size = 16;
void * array[16];
int stack_num = backtrace(array, size);
char ** stacktrace = backtrace_symbols(array, stack_num);
for (int i = 0; i < stack_num; ++i)
{
printf("%s\n", stacktrace[i]);
}
free(stacktrace);
}
创建自己的异常类,用于存储额外的异常信息:错误码,堆栈,错误信息等
class BaseException:public std::runtime_error
{
public:
BaseException(int errorCode, const std::string& errorMessage = "",const std::string &btInfo = "");
~BaseException() throw();
int getErrorCode(void) const;//异常错误码
const char* what() const throw();//异常描述
const char *getBtInfo() const throw();//异常发生时的堆栈
private:
int errorCode;
std::string m_errorMsg;
std::string m_btInfo;
};
BaseException::BaseException(int errorCode, const std::string& errorMessage,,const std::string &btInfo) :
std::runtime_error(errorMessage)
{
m_errorCode = errorCode;
m_errorMsg = errorMessage;
m_btInfo = btInfo;
}
BaseException::~BaseException() throw()
{
}
int BaseException::getErrorCode(void) const
{
return m_errorCode;
}
const char* BaseException::what() const throw()
{
return m_errorMsg.c_str();
}
const char* BaseException::getBtInfo() const throw()
{
return m_errorMsg.c_str();
}
1在程序的最外围,应该用try/catch将程序入口包起来,防止因为有异常没捕捉导致程序core
int main()
{
try
{
...//你的代码
}
catch(BaseException &base_exception)
{
//可以将异常信息写入到日志,以供后面追踪
Log.write(base_exception.getErrorCode(),base_exception.what(),base_exception.getBtInfo());
}
catch(BaseException &std_exception)
{
//可以将异常信息写入到日志,以供后面追踪
Log.write(std_exception.what());
}
catch(...)
{
//可以将异常信息写入到日志,以供后面追踪
Log.write("unknown error");
}
}
2 正确处理new的问题,不应该直接使用系统的new,因为系统级的new有可能会抛出bad_alloc &memExp这种类型的异常,比较合适的做法应该是用创建工厂,例如:
int * createInt()
{
int * tmp = null;
try
{
tmp = new int;
}
catch(bad_alloc &memExp)
{
//这里可以将异常信息捕获,然后抛出去,也可以将错误信息和堆栈信息一起抛出去,这样,在外围捕捉到异常时,可以比较容易确定异常发生的位置
BaseException bad_exception(0,memExp.what(),getStacktrace());
throw bad_exception;
}
}
3 通过new创建的对象,应该用std::shared_ptr之类的只能包装起来,比较安全
4 底层的数据接口,一定要搞明白会不会抛异常,如果会,就需要跟new一样包装一下,不然一旦出错,没有错误信息和堆栈信息,你都不知道发生什么事,比如C++11中的std::stoi,std::stod
就是会抛异常,需要包装一下
5 多线程程序,子线程的异常是不会被主线程的try/catch捕获的,因此每个子线程需要自己捕捉自己线程内部可能发生的异常
6 现在来考虑这样一个构造函数:
Type() : m_a(new TypeA), m_b(new TypeB){}
假设成员变量m_a和m_b是原始的指针类型,并且和Type内的申明顺序一致。这样的代码是不安全的,它存在资源泄漏问题,构造函数的失败回滚机制无法应对这样的问题。如果new TypeB抛出异常,new TypeA返回的资源是得不到释放机会的.
解决方法:
shared_ptr<TypeA> m_a; shared_ptr<TypeB> m_b;这样,我们就可以轻而易举地写出异常安全的代码:
Type() : m_a(new TypeA), m_b(new TypeB){}
}
7 处理进程可能会收到的信号:
进程可能会收到系统发送的信号,如果不处理某些信号,进程的默认响应是终止,因此,为保证正确性,应该正确的处理信号。