【问题标题】:std::nested_exception and polymorphism - Is this the best that can be done?std::nested_exception 和多态性 - 这是可以做到的最好的吗?
【发布时间】:2021-04-22 10:09:24
【问题描述】:

std::nested_exceptions 在您只想调用what() 时很好用,但访问其他异常类型的接口就很难看。

假设我有两个存储一些附加信息的异常类:

/* CODE BLOCK 1 */
class ErrorI : public std::runtime_error {
public:
    ErrorI(int a_integer) : std::runtime_error{"ErrorI"}, integer{a_integer} {}
    int integer;
};

class ErrorD : public std::runtime_error {
public:
    ErrorD(double a_real) : std::runtime_error{"ErrorD"}, real{a_real} {}
    double real;
};

没有嵌套异常,我们可以访问try/catch块中的成员变量:

/* CODE BLOCK 2 */
int main()
{
    try {
        /* do stuff */;
    }
    catch(const ErrorI& ee){
        std::cout << "  Value: " << ee.integer << std::endl;
    }
    catch(const ErrorD& ee){
        std::cout << "  Value: " << ee.real << std::endl;
    }
}

但如果我们想解开std::nested_exception,事情就没有那么简单了。我们需要定义一个递归调用的函数,它应该是这样的:

/* CODE BLOCK 3 */
void process_exception(const std::exception& e, int level=0) {
    try {
        std::rethrow_if_nested(e);
    }
    catch(const std::exception& e) {
        process_exception(e, level+1);
    }

    /* ... process the top-most (latest) exception ... */
}

不幸的是,为了处理最顶层的异常,我们不能使用代码块 2 中的 try/catch 语法:如果我们重新抛出 e,它将被截断为 std::exception,我们将丢失所有附加信息。 编辑:如果使用 std::rethrow_exception 和 std::exception_ptr,则不是这样。

所以我们回到 good-ole 动态类型检查的问题,以及它所需要的一切(例如,参见 this)。

  1. 从具有所需接口的公共基类中派生所有异常。这包括像Visitor pattern 这样的方法。这很简洁,但如果异常类是由外部库提供的,那就不好了。

  2. 使用 dynamic_cast:

     /* CODE BLOCK 4 */
     if (auto p = dynamic_cast<ErrorI const*>(&e)) {
         std::cout << "  Value: " << p->integer << std::endl;
     }
     else if (auto p = dynamic_cast<ErrorD const*>(&e)) {
         std::cout << "  Value: " << p->real << std::endl;
     }
    
  3. ???

我唯一的选择似乎求助于 2。如果有任何其他建议,我很想听听。

【问题讨论】:

  • "如果我们重新抛出 e,它将被截断为 std::exception":我认为这就是 std::exception_ptr 的用途。您可以将其传递给std::rethrow_exception 以在不切片的情况下重新抛出它。
  • 我没有真正看到切片,你是说问题是process_exception 必须在catch 子句中指定异常类型?
  • 如果您不想在process_exception 中放置多个包含所有不同类型的catch 子句,您仍然可以通过指定类型列表来执行类似的操作,例如type_list&lt;ErrorI, ErrorD&gt; 和提供像void process(ErrorI const&amp;); void process(ErrorD const&amp;); 这样的重载集,并具有一些元函数,通过类型列表将嵌套异常分派给重载。
  • Duh :-D 我曾尝试使用 std::make_exception_ptr 进行切片。感谢 cmets!

标签: c++ exception polymorphism dynamic-cast nested-exceptions


【解决方案1】:

std::exception_ptr 的帮助下,您可能会执行以下操作:

void print_exception(const std::exception_ptr& eptr, int level =  0)
{
    try
    {
        std::rethrow_exception(eptr);
    }
    catch (const ErrorI& e)
    {
        std::cerr << std::string(level, ' ') << "exception: " << e.what() << ": " << e.integer << std::endl;
    }
    catch (const ErrorD& e)
    {
        std::cerr << std::string(level, ' ') << "exception: " << e.what() << ": " << e.real << std::endl;
    }
    catch (const std::exception& e)
    {
        std::cerr << std::string(level, ' ') << "exception: " << e.what() << std::endl;
    }
}

// prints the explanatory string of an exception. If the exception is nested,
// recurses to print the explanatory of the exception it holds
void print_exception_rec(const std::exception_ptr& eptr, int level =  0)
{
    print_exception(eptr, level);
    try {
        try {
            std::rethrow_exception(eptr);
        }
        catch (const std::exception& e)
        {
            std::rethrow_if_nested(e);
        }
    } catch(const std::exception&) {
        print_exception_rec(std::current_exception(), level+1);
    } catch(...) {}
}

Demo

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    • 2011-03-14
    • 2017-11-30
    • 2011-07-07
    • 1970-01-01
    相关资源
    最近更新 更多