C++异常:抛出std::string

84

当我的C++方法遇到一些奇怪的情况并且无法恢复时,我希望抛出异常。 抛出 std::string 指针可以吗?

这是我期望要做的:

void Foo::Bar() {
    if(!QueryPerformanceTimer(&m_baz)) {
        throw new std::string("it's the end of the world!");
    }
}

void Foo::Caller() {
    try {
        this->Bar(); // should throw
    }
    catch(std::string *caught) { // not quite sure the syntax is OK here...
        std::cout << "Got " << caught << std::endl;
    }
}

28
这是合法的,但并不道德。 - Marcin
18
你有一个内存泄漏。是谁删除了被抛出的字符串指针?不要使用指针来处理异常。 - Fernando N.
2
我知道有点晚了,但是无论如何,这篇文章对这个问题提出了一些观点 http://www.boost.org/community/error_handling.html - Alex Kreimer
7个回答

104

是的。C++标准库中有一个基础异常类std::exception。您可能希望避免使用字符串作为异常类,因为它们本身在使用过程中可能会抛出异常。如果发生这种情况,那么你会在何处?

Boost有一份关于异常和错误处理的良好样式的文档,值得一读。


22
附注:如果异常对象本身抛出异常,将调用std::terminate函数,这就是你会停留的地方(而且情况不太妙!) - Alaric
6
有人认为,在内存分配时添加异常处理是浪费时间的,可以参考 http://www.gotw.ca/publications/mill16.htm。另一个反对这种观点的论据是,标准库中的 std::runtime_exception 和相关函数已经这样实现了,所以你为什么不用呢? - Greg Rogers

68

几个原则:

  1. 如果有一个std::exception基类,你应该让你的异常派生自它。这样一来,通用异常处理程序仍然有一些信息。

  2. 不要抛出指针,而是对象,这样内存就会被处理。

例如:

struct MyException : public std::exception
{
   std::string s;
   MyException(std::string ss) : s(ss) {}
   ~MyException() throw () {} // Updated
   const char* what() const throw() { return s.c_str(); }
};

然后在你的代码中使用它:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw MyException("it's the end of the world!");
  }
}

void Foo::Caller(){
  try{
    this->Bar();// should throw
  }catch(MyException& caught){
    std::cout<<"Got "<<caught.what()<<std::endl;
  }
}

5
从std::runtime_exception派生不会更好吗? - Martin York
请注意,christopher_f的论点仍然有效:您的异常在构造时可能会引发异常...这是值得思考的问题... :-D ...我可能错了,但是异常应该通过它们的const引用捕获,不是吗? - paercebal
对于const引用,这是可能的,但不是强制的。我曾经思考过一段时间……没有找到任何支持或反对的参考资料。 - PierreBdR
我尝试了这段代码,但出现了编译错误,关于析构函数的问题... - dividebyzero
尝试添加以下代码:~MyException() throw () {},用于析构函数。 - PierreBdR
显示剩余2条评论

26
所有这些工作都是可以完成的:
#include <iostream>
using namespace std;

//Good, because manual memory management isn't needed and this uses
//less heap memory (or no heap memory) so this is safer if
//used in a low memory situation
void f() { throw string("foo"); }

//Valid, but avoid manual memory management if there's no reason to use it
void g() { throw new string("foo"); }

//Best.  Just a pointer to a string literal, so no allocation is needed,
//saving on cleanup, and removing a chance for an allocation to fail.
void h() { throw "foo"; }

int main() {
  try { f(); } catch (string s) { cout << s << endl; }
  try { g(); } catch (string* s) { cout << *s << endl; delete s; }
  try { h(); } catch (const char* s) { cout << s << endl; }
  return 0;
}

你应该优先选择h而不是f和g。请注意,在最不可取的选项中,您需要显式释放内存。


1
但是把一个 const char 指针抛出到一个局部变量不是一个 bug 吗?是的,当然我知道编译器会把 c-string 放在未修改的区域,这个地址在应用程序运行之前不会改变。但这是未定义的;更重要的是,如果这段代码在一个库中,在抛出错误后就消失了,那会怎样呢?顺便说一下,我在我的项目中也做了很多这样糟糕的事情,我只是一个学生。但我应该考虑过这个问题... - Hi-Angel
1
@Hi-Angel,这里没有局部变量;抛出的是一个字符串字面值,在标准中有特定和明确定义的生命周期处理方式,你的担忧是无效的。请参见例如 https://dev59.com/no_ea4cB1Zd3GeqPQ6Tp#32872550 如果这里有问题,基本上任何消息的抛出都不能工作(至少不需要额外的杂技/风险),所以当然没有问题,一切都好。 - underscore_d

8
它可以工作,但如果我是你的话,我不会这样做。你似乎没有在完成后删除堆数据,这意味着你创建了一个内存泄漏。C++编译器会确保即使堆栈被弹出,异常数据也能够保持活动状态,因此不必使用堆。
顺便说一下,抛出std::string不是最好的方法。如果您使用一个简单的包装对象,将来会有更多的灵活性。它现在可能只封装一个字符串,但也许将来您会想包括其他信息,比如导致异常的一些数据或者行号(非常常见)。您不希望在代码库中的每个位置都更改异常处理,所以现在采取高路,不要抛出原始对象。

8

除了可能会抛出从std::exception派生的东西,您还应该抛出匿名临时对象并通过引用捕获:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw std::string("it's the end of the world!");
  }
}

void Foo:Caller(){
  try{
    this->Bar();// should throw
  }catch(std::string& caught){ // not quite sure the syntax is ok here...
    std::cout<<"Got "<<caught<<std::endl;
  }
}
  • 你应该抛出匿名临时对象,让编译器处理抛出对象的生命周期 - 如果你抛出从堆上 new 出来的对象,那么其他人就需要释放这个对象。
  • 你应该捕获引用以防止对象切割。

.

有关详细信息,请参阅 Meyer 的 "Effective C++ - 第三版" 或访问 https://www.securecoding.cert.org/.../ERR02-A.+Throw+anonymous+temporaries+and+catch+by+reference


4

C++中最简单抛出异常的方法:

#include <iostream>
using namespace std;
void purturb(){
    throw "Cannot purturb at this time.";
}
int main() {
    try{
        purturb();
    }
    catch(const char* msg){
        cout << "We caught a message: " << msg << endl;
    }
    cout << "done";
    return 0;
}

这将打印:

We caught a message: Cannot purturb at this time.
done

如果您捕获了抛出的异常,该异常将被包含在内,程序将继续运行。如果您未捕获异常,则程序将退出并打印:

此应用程序已请求 Runtime 以不寻常的方式终止它。请联系应用程序的支持团队获取更多信息。


4
这似乎是个不好的想法 - catch (std::exception&) 无法捕获它。 - Timmmm

3
虽然这个问题已经比较老并且已经有了答案,但是我想补充一下如何在C++11中进行适当的异常处理:使用std::nested_exceptionstd::throw_with_nested。在我的观点中,使用它们可以实现更清晰的异常设计,而且不需要创建异常类层次结构。请注意,这使得您可以在代码内部获取异常的回溯,而无需调试器或繁琐的日志记录。在StackOverflow上描述了如何编写一个适当的异常处理程序,它将重新抛出嵌套的异常,可以参考这里这里
由于您可以对任何派生的异常类执行此操作,因此可以向这样的回溯中添加大量信息!您还可以查看我在GitHub上的MWE,其中回溯将类似于以下内容:
Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接