(不)在异常中使用std :: string

时间:2013-04-05 09:59:41

标签: c++ exception boost

我一直在读,我不应该抛出std::string或其他一些分配内存的类。比较here或更重要的是{3}}在第3点。 - 不要嵌入std::string对象

所以现在我正在尝试将here插入到我的项目中,我看到了什么:boost::exception

为什么不提升符合自己的建议?

如果我的参数不能硬编码,比如在配置文件中安装,我怎样才能将它们放入异常中而不使用std::string

或指南不使用std::string 只有尽可能少使用std::string 指南?我有点困惑......

我做了一些研究。如果我错了,请纠正我。


如果我理解正确的话,那就是关于投掷期间的分配以及分配的内存发生了什么。因此,如果我在构造函数中分配内存并且无法在异常的析构函数中释放内存,则内存会丢失,这将产生内存泄漏。但是在投掷之前分配它是可以的,所以异常是干净的。

我试过这个:

struct xexception {
  int *ttt[10];
  xexception() {
    ttt[0] = new int[0xfffffffL];
    ttt[1] = new int[0xfffffffL];
    ttt[2] = new int[0xfffffffL];
    ttt[3] = new int[0xfffffffL];
    ttt[4] = new int[0xfffffffL];
    ttt[5] = new int[0xfffffffL];
    ttt[6] = new int[0xfffffffL];
    ttt[7] = new int[0xfffffffL];
    ttt[8] = new int[0xfffffffL];
    ttt[9] = new int[0xfffffffL];
  }

  ~xexception() throw() {
    //never happen
    delete[] ttt[0];
    delete[] ttt[1];
    delete[] ttt[2];
    delete[] ttt[3];
    delete[] ttt[4];
    delete[] ttt[5];
    delete[] ttt[6];
    delete[] ttt[7];
    delete[] ttt[8];
    delete[] ttt[9];
  }
};

int main(int argc, const char *argv[]) {
  try {
    throw(xexception());
  }
  catch (const xexception &e) {
    std::cerr << "\nttt " << e.ttt[0][0] << std::endl;
  }
  catch (std::bad_alloc) {
    std::cerr << "bad alloc" << std::endl;
  }

  return 0;
}

结果是,我得到了 bad_alloc 以及巨大的内存泄漏。

现在,如果我之前进行了分配,那么会在创建异常之前抛出bad_alloc。


我对例外概念的例外是:

谁在乎?如果我的程序中有bad_alloc,由于memory_leak或其他东西(我在谈论PC上的程序而不是微控制器)我还有其他问题。也许我可以发现bad_alloc发生了,但在哪里?在函数(可能是1000之一)或std::string中的我的alloc(我知道它是字符串但是......没有可能操纵字符串的内存......或者它的消散)。

try {
  // where is the error???
  int *x = new int[100];  // here?
  ....
  int *y = new int[100];  // or here?
  ....
  int *z = new int[100];
  ....
  int *w = new int[100];
  ....
  int *t = new int[100];
  ....
  int *f = new int[100];

  ....

  std::string str("asdfasdfasdfasdfasdfasdfasdf"); // maybe here
}
catch (the error) {
  ....
}

然后呢?我想弄清楚它发生在哪里吗?因此我会使用 valgrind 而不是例外。

void foo() {
  int *i = new int[1];
  foo();
}

try {
  foo();
}
chatch( bad_boy ) {
  go_exception_handler_go(parameters); // oh, shit happens: also an stack_overflow may happend, cause stack is also full
}

或者我应该操纵错误消息并记录它,最终会抛出下一个bad_alloc。

请不要误解我。因为我已经看过boost :: exception我已经重写了我的异常类(直到等待回答)但我也认为没有必要拿起每一粒沙子。

2 个答案:

答案 0 :(得分:18)

该建议基本上告诉您“不要使用任何可能在异常中引发异常的构造”。那是因为如果在尝试抛出异常时遇到异常,C ++运行时将立即调用terminate()并终止您的程序。

现在,如果涉及的(或者)异常只是调用terminate()(就像未捕获的异常的默认值那样),那么你真的不需要担心它。例如,如果您的应用程序无法处理bad_alloc(无法从内存不足中恢复),那么您不必担心可能的复制构造函数(例如std::string)抛出它。

但是如果您希望能够从bad_alloc捕获并恢复,则需要确保没有任何异常复制构造函数可以导致一个。如果您正在编写其他应用程序将使用的库,则不应假设该应用程序不想处理bad_alloc

C ++ 11通过尽可能使用移动构造函数(而不是复制构造函数)使这变得更容易。由于std::string的移动构造函数从不抛出异常,因此只要正确实现移动构造函数并确保使用它们,就可以在异常类型中安全地使用std:string。请注意,要在throw表达式中抛出的对象的初始构造不是异常抛出过程的一部分,因此构造函数可以抛出异常而不会导致双重异常(和terminate())。所以如果你有:

throw some_function();

some_function可能会抛出异常(例如bad_alloc)而不返回要抛出的对象,这很好。如果它没有抛出异常(并返回一个有效的对象),那么异常类型的移动构造函数将被用于异常抛出过程(如果可用),并且该移动构造函数不能抛出异常。


完全独立于上述内容,每当您致电new时,您需要确保在每种情况下只有一个地点会调用delete,否则您将泄漏内存(或双重删除时崩溃) )。只要你有一个调用new的函数然后做一些可能引发异常的事情(比如再次调用new),这就变得很棘手。如果在构造函数中发生这种情况,则不会调用该对象的析构函数(虽然基类和字段的析构函数将是),因此您不能在析构函数中进行清理,因为您正在尝试使用您的示例。 / p>

幸运的是std::unique_ptr存在使这更容易。如果您将异常类编写为:

struct xexception {
  std::unique_ptr<int[]> ttt[10];
  xexception() {
    ttt[0].reset(new int[0xfffffffL]);
    ttt[1].reset(new int[0xfffffffL]);
    ttt[2].reset(new int[0xfffffffL]);
    ttt[3].reset(new int[0xfffffffL]);
    ttt[4].reset(new int[0xfffffffL]);
    ttt[5].reset(new int[0xfffffffL]);
    ttt[6].reset(new int[0xfffffffL]);
    ttt[7].reset(new int[0xfffffffL]);
    ttt[8].reset(new int[0xfffffffL]);
    ttt[9].reset(new int[0xfffffffL]);
  }
};

它应该工作,而不是泄漏记忆。

答案 1 :(得分:5)

虽然我认为不使用std::string作为核心,但基本异常可能是一个很好的指导原则,我不认为面向用户的库/应用程序必然会遵循这一点。

可能还有其他原因,但是您点击了主要原因:您希望向用户(或开发人员)指出上下文有意义的信息,而这些信息通常只能用文字字符串来完成。必须进行动态分配才能执行此操作。如果出于某种原因,你有一个bad_alloc,那么你可能已经开始使用它,所以它不会给你买任何东西。

修改

顺便说一句:std::exception的析构函数因某种原因被标记为虚拟!