如何从C++中的构造函数中捕获异常

How to catch exceptions from a constructors in C++

本文关键字:捕获异常 构造函数 C++      更新时间:2023-10-16

假设我们有一个可能会抛出异常的构造函数。

class A{
public:
    A(); // may throw exceptions
};

我们可以使用这种方式来捕获异常:

try{
    A a;
    // do somethings to a, and I have to put everything about a here
}catch(...){
    // handle exceptions
}

所以我的问题是如何避免在不使用指针的情况下将所有内容都放在try-catch块中。

您可以定义一个为您处理异常的创建函数,并在发生异常时返回合理的默认值,例如:

struct A
{    
    A() : data("Default")
    {
        std::cout << "In A()" << std::endl;
    }
    A(const A& other) : data(other.data)
    {
        std::cout << "In A(A)" << std::endl;
    }
    A(bool param) 
    {
        std::cout << "In A(bool)" << std::endl;
        if(param)
            throw std::runtime_error("Failed");
        data = "Hello";
    }
    std::string data;
};
A createA(bool param, A& def_a)
try
{
    return A(param);
}
catch (...) {
    //...
    return def_a;
}

然后,您可以使用此函数的返回值启动实际A。由于 RVO,如果创建成功,则不会执行任何复制,只有在创建失败时才执行(因为它必须复制默认值):

int main(int argc, char**args) 
{
    A defA;
    A a1 = createA(true, defA);
    A a2 = createA(false, defA);
    std::cout << "A1: " << a1.data << std::endl;
    std::cout << "A2: " << a2.data << std::endl;
    return 0;
}

其输出为:

In A()
In A(bool)
In A(A)
In A(bool)
A1: Default
A2: Hello

第一个构造函数是默认值。然后,您可以看到在第一次A(bool)调用(抛出)之后,进行了复制构造函数调用以返回默认值。第二个调用不会失败,没有复制构造函数调用(由于 RVO)。在此之后,您最终会得到一个默认A和一个成功创建的A,您可以在此后使用。

当然,如果复制构造函数也可以抛出,那么你可能会有一个异常转义createA - 如果是这种情况,你将不得不稍微修改这个设计。

一种可能的方法是使用哨兵。

class sentry {
public:
    bool exception_thrown=true;
    void constructed()
    {
        exception_thrown=false;
    }
    ~sentry()
    {
        if (exception_thrown)
        {
           // Whatever you want to do
        }
    }
};

然后:

sentry a_sentry;
A a;
a_sentry.constructed();

在哨兵的析构函数中,如果设置了exception_thrown,那只能是因为在A的构造函数中抛出了异常,因为该标志在完全构造后立即被清除A。因此,将清理代码放在析构函数中。使用此方法时必须小心的一件事是析构函数本身不能引发自己的异常。

然后,您还可以执行以下操作:

class A_with_sentry : public sentry, public A {
public:
      A_with_sentry()
      {
           constructed();
      }
};

然后只需声明:

A_with_sentry a;

然后,更进一步,使其成为模板函数,让构造函数将其可变参数完美地转发给 sentry 对象的构造函数,等等......

还有其他方法,但不建议这样做,因为您需要处理指针。

A* a=nullptr;
try {
    a = new A();
}
catch(...){
}
if(a){
    //do somethings to a, and I have to put everything about a here
    delete a;
}