如何捕获构造函数异常

How to catch a constructor exception?

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

我有一个C++类,它在失败时从构造函数抛出异常。如何分配此类的本地实例(不使用new)并处理任何可能的异常,同时保持try块范围尽可能小?

从本质上讲,我正在寻找与以下Java习语等价的C++:

boolean foo() {
    Bar x;
    try {
        x = new Bar();
    } catch (Exception e) {
        return false;
    }
    x.doSomething();
    return true;
}

我不想从x.doSomething()捕获异常,只捕获构造函数

我想我正在寻找一种方法来分离x的声明和初始化。

是否可以在不使用堆分配和指针的情况下实现这一点?

您可以使用C++17:中的std::optional

bool foo() {
    std::optional<Bar> x; // x contains nothing; no Bar constructed
    try {
        x.emplace();      // construct Bar (via default constructor)
    } catch (const Exception& e) {
        return false;
    }
    x->doSomething();     // call Bar::doSomething() via x (also (*x).doSomething() )
    return true;
}

这个Java习惯用法不能很好地转换为C++,因为Bar x;将需要默认构造函数,即使您的实际构造函数需要传递参数。

我建议在这种程度上与语言作斗争-扩大try块就足够了-但如果真的想缩小范围,那么可以使用一个函数,并依靠返回值优化来避免值复制:

Bar foobar()
{
    try {
        return Bar();
    } catch (Exception& e){
        /* Do something here like throwing a specific construction exception
           which you intercept at the call site.*/
    }
}

但实际上,您可以在构造上抛出一个特定的异常,从而完全避免这种函数方法。

是的,如果您将所有代码都放在try子句中,例如通过使用函数try块(以避免不必要的嵌套和作用域),这是可能的:

bool foo() try
{
    Bar x;
    x.doSomething();
    return true;
}
catch (std::exception const& e)
{
    return false;
}

或者在try子句中调用另一个真正起作用的函数:

void real_foo()
{
    Bar x;
    x.doSomething();
}
bool foo() try
{
    real_foo();
    return true;
}
catch (std::exception const& e)
{
    return false;
}

请注意,在构造函数中抛出异常通常不是一个好主意,因为这会停止对象的构造,并且不会调用其析构函数。


正如Holt所指出的,这也将捕获来自doSomething调用的异常。有两种解决方法:

  1. 简单而标准的方法:使用指针

  2. 使用两阶段构造:有一个不能抛出异常的默认构造函数,然后调用一个可以抛出异常的特殊"构造"函数。

第二种方法在C++标准化之前很常见,并在Symbian系统的代码中广泛使用。这种情况已经不常见了,因为使用指针更容易、更简单,尤其是在今天有了好的智能指针的情况下。我真的不推荐现代C++中的第二种方法。

当然,最简单的方法是确保构造函数根本不会抛出异常,或者如果抛出了异常,那么它们的性质是程序无论如何都无法继续,并终止程序。正如在对您的问题的评论中所指出的,C++中的异常是昂贵的,然后我们还有被放弃的构造问题,并且在所有情况下,C++中使用异常只能在特殊情况下进行。C++不是Java,即使两种语言中有相似的结构,也不应该这样对待它。

如果你仍然想从构造函数抛出异常,实际上有第三种方法可以只捕获这些异常:使用上面的一个代码示例,只抛出doSomething永远不能抛出的特定异常,然后只捕获这些特定的构造函数。

通常,如果您想避免堆分配,则不能将局部变量的声明与其定义分开。因此,如果要将所有内容组合到一个函数中,则必须使用try/catch块包围x的整个范围:

boolean foo() {
    try {
        Bar x;
        x.doSomething();
    } catch (Exception e) {
        return false;
    }
    return true;
}

编号。从您的java示例中,您将不得不在以下两种可能性之间进行选择:

无指针:

bool foo() {
    try {
        Bar x;
        x.doSomething();
    } catch (Exception e) {
        return false;
    }
    return true;
}

带指针:

bool foo() {
    Bar* x = nullptr;
    try {
        x = new Bar();
    } catch (Exception e) {
        return false;
    }
    x->doSomething();
    delete x; // don't forget to free memory
    return true;
}

或者使用托管指针:

#include <memory>
bool foo() {
    std::unique_ptr<Bar> x;
    try {
        x = new Bar();               // until C++14
        x = std::make_unique<Bar>(); // since C++14
    } catch (Exception e) {
        return false;
    }
    x->doSomething();
    return true;
}

您必须在的变体之间进行选择

bool foo() {
    std::unique_ptr<Bar> x;
    try {
        x = std::make_unique<Bar>();
    } catch (const BarConstructorException& e) {
        return false;
    }
    x->doSomething();
    return true;
}

bool foo() {
    try {
        Bar x;
        x.doSomething();
    } catch (const BarConstructorException& e) {
        return false;
    }
    return true;
}

在修订后的问题中,OP增加了的要求

"我不想从x.doSomething()捕获异常,只想捕获[局部变量]的构造函数。

翻译Java代码的简单方法

boolean foo() {
    Bar x;
    try {
        x = new Bar();
    } catch (Exception e) {
        return false;
    }
    x.doSomething();
    return true;
}

…对于C++,则使用Optional_类(如Barton Nackmann Fallibleboost::optional或C++17 std::optional

auto foo()
    -> bool
{
    Optional_<Bar> xo;
    try
    {
        xo.emplace();
    }
    catch( ... )
    {
        return false;
    }
    Bar& x = *xo;
    // Possibly other code here, then:
    x.doSomething();
    return true;
}

一个很好的替代方案是重构代码,如下所示:

struct Something_failure {};
void do_something( Bar& o )
{
    // Possibly other code here, then:
    o.doSomething();
}
auto foo()
    -> bool
{
    try
    {
        Bar x;
        do_something( x );
        return true;
    }
    catch( Something_failure const& )
    {
        throw;
    }
    catch( ... )
    {}
    return false;
}

如果您不喜欢上面的方法,那么您可以始终使用动态分配的Bar实例,例如使用std::unique_ptr来保证清理,但是这具有动态分配的一般开销。在Java中,大多数对象都是动态分配的,所以这似乎不是一个严重的缺点。但在C++中,大多数对象都是超高速堆栈分配的,因此与普通操作相比,动态分配是一个非常慢的操作,因此必须权衡动态分配可能在概念上的简单性。