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

C++ (不)在异常中使用std::string,c++,exception,boost,C++,Exception,Boost,我一直在读,我不应该抛出std::string或其他分配内存的类。就像或更重要的是第3点不要嵌入std::string对象 所以现在我正试图插入到我的项目中,我看到了什么: 为什么boost不遵守自己的建议 如果我有不能硬编码的参数,比如配置文件中的safed,我怎么能在不使用std::string的情况下将它们放入异常呢 或者指南不使用std::string只是尽可能少使用std::string指南?我有点困惑 我做了一些研究。如果我错了,请纠正我 如果我理解正确的话,这一切都是关于抛出期间

我一直在读,我不应该抛出
std::string
或其他分配内存的类。就像或更重要的是第3点不要嵌入
std::string
对象

所以现在我正试图插入到我的项目中,我看到了什么:

为什么boost不遵守自己的建议

如果我有不能硬编码的参数,比如配置文件中的safed,我怎么能在不使用
std::string
的情况下将它们放入异常呢

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

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


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

我试过这个:

然后呢?我要试着弄清楚发生在哪里吗?因此,我将使用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
}
或者我应该操纵错误消息并记录它,下一个错误消息最终会抛出什么


请不要误解我的意思。因为我看到了boost::exception,所以我重写了我的exception类(直到等待答案),但我也认为没有必要收集每一粒沙子。

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

可能还有其他原因,但您找到了主要的原因:您希望向用户(或开发人员)指示有上下文意义的信息,这通常不能仅使用文字字符串。为此,必须进行动态分配。如果,由于某种原因,你有一个坏的分配,你可能已经开始用水管了,所以它不会买/丢你任何东西

编辑:


顺便说一下:
std::exception
的析构函数被标记为virtual是有原因的

建议基本上是告诉您“不要使用任何可能在异常中引发异常的构造”。这是因为如果在抛出异常时遇到异常,C++运行时将立即调用<代码>终端()/Cyto>并杀死程序。 现在,如果(其中一个)所涉及的异常只是调用
terminate()
(这是未捕获异常的默认值),那么您就不必担心它了。例如,如果您的应用程序无法处理
bad\u alloc
(无法从内存不足中恢复),那么您就不需要担心可能抛出它的复制构造函数(例如
std::string

但是,如果您希望能够捕获并恢复
错误的\u alloc
,则需要确保没有任何异常副本构造函数会导致错误。如果您正在编写一个其他应用程序将使用的库,则不应假定应用程序不希望处理
bad\u alloc

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

throw some_function();
某些函数
可能会在不返回要抛出的对象的情况下抛出异常(例如
bad\u alloc
)。如果它不引发异常(并返回有效对象),则异常类型的移动构造函数将用于异常引发过程(如果可用),并且该移动构造函数不得引发异常


完全独立于上述情况,无论何时调用
new
,您都需要确保在每种可能的情况下,只有一个点会调用
delete
,否则您将泄漏内存(或因双重删除而崩溃)。当您有一个函数调用
new
,然后执行其他可能引发异常的操作(例如再次调用
new
)时,这就变得很棘手了。如果在构造函数中发生这种情况,则不会调用对象的析构函数(尽管基类和字段的析构函数将被调用),因此您不能像处理示例那样在析构函数中进行清理

幸运的是,
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]);
  }
};
struct xexception{
std::unique_ptr ttt[10];
xexception(){
ttt[0]。重置(新int[0xfffffffL]);
ttt[1]。重置(新int[0xfffffffL]);
ttt[2]。重置(新int[0xfffffffL]);
ttt[3]。重置(新int[0xfffffffL]);
ttt[4]。重置(新int[0xfffffffL]);
ttt[5]。重置(新int[0xfffffffL]);
ttt[6]。重置(新int[0xfffffffL]);
ttt[7]。重置(新int[0xfffffffL]);
ttt[8]。重置(新int[0xfffffffL]);
ttt[9]。重置(新int[0xfffffffL]);
}
};

它应该可以工作,不会泄漏内存。

我没有看到任何
std::string
s是
exception
的成员
std::runtime\u error
使用
std::string
。所以使用字符串作为参数是可以的吗?
std::runtime\u error
有一个引用字符串的参数。它没有说它存储了一个(或创建了任何副本)。这个“规则”来自一个overbl
throw some_function();
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]);
  }
};