C++ 是否处理从构造函数引发的异常?

C++ 是否处理从构造函数引发的异常?,c++,exception,constructor,C++,Exception,Constructor,tl;博士 我想这样做: foo() { Y* p; try { p = new Y(); } catch { //fix any problem that may have occured } // Now I know the object was fixed during catch or simply was created successfully. p.do_something(); // win } void f() { X x;

tl;博士

我想这样做:

foo()
{
  Y* p;
  try {
    p = new Y();
  } catch {
    //fix any problem that may have occured
  }
  // Now I know the object was fixed during catch or simply was created successfully.
  p.do_something();
  // win
}
void f()
{
  X x;             //← if X::X() throws, the memory for x itself will not leak
  Y* p;
  try{  
    p = new Y();  //← if Y::Y() throws, the memory for *p itself will not leak
  } catch(exception &e){
    ///clean what you need

    throw e;
  }
}
我正在创建的对象正在使用一个有时不可靠的永久资源。在没有必要预防措施的情况下尝试分配是危险的。如果它真的失败了,有些事情是可以做的。然而,抛出一个与我稍后展示的switch语句不同的异常似乎不允许我立即修复这个问题

有人建议我从构造函数中抛出一个异常。然而,我不明白这是如何被证明是有用的,因为我不知道如何处理这个异常?我做了一次搜索并找到了,但我不确定这是否真的是处理构造失败的一种有用的可扩展方法

示例代码:

void f()
{
  X x;             //← if X::X() throws, the memory for x itself will not leak
  Y* p = new Y();  //← if Y::Y() throws, the memory for *p itself will not leak
}
假设堆上有更多实例是在
p
之前分配的。他们不会因为这个而泄露到记忆中吗?因此函数
f
只是用来构造
Y
的一个实例。将“危险”的建筑移到外面,换一种方法,不是总是更有用吗

我通常做的是:

X* x = new X(); //No dangerous operation

switch (x.init()) // init returns int
  {
    case ...
    // Handle failed init() here
  }

这有缺点吗?它似乎更可靠。

如果抛出可能导致泄漏,则删除分配是您的责任

您可以按如下方式滚动异常:

foo()
{
  Y* p;
  try {
    p = new Y();
  } catch {
    //fix any problem that may have occured
  }
  // Now I know the object was fixed during catch or simply was created successfully.
  p.do_something();
  // win
}
void f()
{
  X x;             //← if X::X() throws, the memory for x itself will not leak
  Y* p;
  try{  
    p = new Y();  //← if Y::Y() throws, the memory for *p itself will not leak
  } catch(exception &e){
    ///clean what you need

    throw e;
  }
}
您应该在每个成员上使用(资源获取是初始化)。仅当构造函数已完成时,才会调用对象的析构函数。这样,如果只调用两个成员的构造函数中的一个,则只调用该成员的析构函数,在该析构函数中应执行任何清理

class Locked_file_handle {
    File_ptr p;
    unique_lock<mutex> lck;
public:
    X(const char* file, mutex& m)
        :p{file, "rw"},
        lck{m} 
    {}
    // ..
};

您的示例是安全的,因为如果c'tor抛出异常,则分配给未能构造的对象的内存将在传递异常之前释放。当异常退出
f
范围时,堆栈将展开

如果一个半构造并随后将其成员和基销毁的对象返回到调用作用域,则该作用域将无法确定如何正确释放该内存,所以谢天谢地,这并没有完成


如果在同一函数中在堆上构造了其他对象,则通常的规则适用。原则上,这里不需要特殊情况的c'tor调用,只需假设抛出的语句是常规函数调用。

不要使用
new
。然后问题就消失了。或者,如果您必须使用
new
,请使用
unique\u ptr
shared\u ptr
,这样当您离开作用域时,内存就被释放了。更好的做法是重新包装所有资源,而不仅仅是内存。也许您在构造函数中遇到了问题。如果你通过抛出一个异常来解决这个问题,那么你有两个问题。这个问题与新问题无关。即使您在堆栈上构造一个对象或将一个对象插入到容器中,构造函数仍然会执行。但是,如果根本问题无法在函数中解决,那么抛出异常又有什么意义呢?这难道不像用另一个抽象来包装构造函数,将繁重的工作留给其他人吗?因为构造函数不能返回值,这是报告失败的最合乎逻辑的方式。调用不做任何“危险”的构造函数,然后调用公共init()不是更合逻辑吗方法,该方法将返回信息(如果失败)。我将使用一个
init()
函数将这一点添加到问题中,该函数是不受欢迎的,即使是语言作者Stroustrup本人也不赞成。当构造函数完成执行时,应该留给您一个完全可用的对象。使用init函数没有任何好处。无论哪种方式,您都可能需要处理异常。如果init分配内存,然后抛出,那么你会做什么,这与构造函数抛出时会做的有什么不同?创建一个init函数只会给你一种错误的感觉,认为一切都很好,但你仍然需要做同样的清理工作。对于init函数,您还有更多的工作要做,因为现在您必须销毁*p。但是,在构造函数中,设计器仍应尝试确保资源被释放。如果构造器分配了5件事情,然后在执行第6件事情时抛出,该怎么办?然后可能会发生内存泄漏,除非构造函数中有一个try..catch在重试之前进行了一些清理。当然。在这方面,c'tor与任何其他函数都没有什么不同。您能否评论一下此代码示例中与RAII相关的内容,以及如何使可能被其迷惑的人受益?AFAIK RAII意味着需要在X的DTOR期间释放锁?在上面的构造函数中唯一可能发生异常的地方是在这两个本地对象的构造函数之一中-p和lck。我们假设这两个对象的析构函数都正确地释放了任何资源(根据资源的不同而有所不同),或者随后也在其成员上使用RAII。