【问题标题】:Introduce an additional base type in an exception hierarchy in C++?在 C++ 的异常层次结构中引入额外的基类型?
【发布时间】:2015-01-07 16:57:46
【问题描述】:

我们的基本情况如下:

// 3rd party lib:

namespace ns3p {
  class OperationException : public std::exception; // yes, no `virtual` here
}

// our code:

// wrapper with additional information to the ns3p::OperationException
class ModuleOpException : public ns3p::OperationException;

// our catch sites:
// mostly:
catch (ModuleOpException const& ex) { ...
// but sometimes:
catch (ns3p::OperationException const& ex) { ...

现在,这增加了包括额外的异常,所有这些都来自 ModuleOpException与来自 3rd 方库的任何错误有任何关系,但只是在相同的上下文中抛出使用 3rd 方库中的东西的地方。

// This indirectly derives from ns3p::OperationException, even though 
// there is *no* underlying OperationException at all. It just thrown "stand alone"
// and caught by ModuleOpException& :
class InvalidXYZException : public ModuleOpExcpetion;

我们现在已经考虑“反转”层次结构以更好地反映实际情况,并且这样做(最初)对其他代码的影响最小。

我们计划这样做:

// new base exception type:
class ModuleBaseException : public virtual std::exception;

// changed to derive both from our base type as well as the 3rd party type:
class ModuleOpException : public virtual ModuleBaseException, public virtual ns3p::OperationException;

// only derives from the base type:
class InvalidXYZException : public virtual ModuleBaseException;

// all catch sites change the catch of `ModuleOpException` to:
catch (ModuleBaseException const& ex) { ...
// and leave the catch ns3p::OperationException alone

这应该可以工作(应该吗?),除了我不确定第三部分异常类型的std::exception 的非虚拟继承有多少会搞砸。我认为我们在运行时绑定as long as noone tries to catch(std::exception const&) in which case the catch would fail 是安全的,因为转换是不明确的。

这似乎是一个可行的解决方案?或者尝试将non-virtual-std::exception 类型与上述层次结构集成是一个“非常糟糕的主意”?

注意:我们可以更改第 3 方库以从 std::exception 中“正确”派生 virtual 的可能性为零(如在 0.00% 中)。


  • 当然,如果之前版本中的任何catch(ns3p::OperationException&)“意外”捕获了InvalidXYZExecption,这将在现在中断,但这是可以接受的。

【问题讨论】:

  • 为什么需要这么多异常类,为什么需要 ModuleOpException 派生自第 3 方库异常类?

标签: c++ exception exception-handling multiple-inheritance


【解决方案1】:

try...catch 应该发生在将要正确处理异常的地方,或者如果此时将修改异常并重新抛出异常。

使用不同异常类的目的是以不同的方式处理它们。 (我认为这是一个坏主意,打开某种代码会更好,并且您可能仅在极少数情况下根据类型更改您的操作,例如如果错误是暂时的则重试请求)。

无论如何,我认为第 3 方库用于帮助实现您自己的库。您的库的用户不想知道您用于实现的库中的异常。

因此,您应该从您的库的用户那里抽象出来,方法是捕获其中的任何一个并将它们重新作为您自己的,以用户期望的方式。

从您的库中抛出的异常与您的函数的返回值一样是“合同”的一部分。

因此,用户可能希望捕获 ModuleBaseException 或 ModuleOpException,但不会捕获 ns3p::OperationException。

您自己的代码应该捕获它们,可能将它们转换为您的 ModuleOpException 类型并抛出它。

【讨论】:

  • 所以,本质上你是在说:不要派生自第 3 方类型”?
  • 在这种情况下,我看不到您这样做的好处。您正在使用 3rd 方库,他们没有使用您的代码,除了您之外,没有人在这里捕获他们的异常类型。
  • 想一想,不是从第三方派生,可能最好的解决方案。谢谢。
  • 当我在做的时候,因为你写了“......以不同的方式处理它们。(我认为这是一个坏主意......”你可能会喜欢@ 987654321@ 完全不相关的一块 :-)
【解决方案2】:

恕我直言,它应该可以工作。 C++ 规范说:

如果...处理程序是 cv T 或 cv T& 类型且 T 是 E 或 ... 的明确公共基类,则处理程序是 E 类型异常对象的匹配项。

try 块的处理程序按出现的顺序进行尝试。

只要你在ns3p::OperationException 之前捕获ModuleBaseException,任何源自ModuleBaseException 的异常都应该被正确的处理程序捕获,即使它也源自ns3p::OperationException

唯一的问题是 std::exception 中的所有方法和字段都将在您的异常类中重复,您应该始终将它们限定为来自 ModuleBaseException 以避免歧义。您可能应该使用一个通用实用程序来处理将在所有处理程序中使用的异常。

【讨论】:

  • 在语法级别或任何可行的情况下。但是在这种情况下,我认为用户对层次结构以及捕获什么异常感到有些困惑
【解决方案3】:

从现有的异常层次结构派生时,您有多重继承(一个用于您自己的,一个用于现有的层次结构)。为避免歧义,您可以传递并保留指向现有层次结构的指针:

#include <stdexcept>
#include <sstream>

// Error
//=============================================================================

/// Base class for all exceptions in a library.
/// \NOTE The constructor requires a std::exception as argument
class Error
{
    // Construction
    // ============

    public:
    virtual ~Error() noexcept {};

    protected:
    Error(std::exception& exception)
    :   m_exception(&exception)
    {}


    // Element Access
    // ==============

    public:
    const char* msg() const { return m_exception->what(); }

    // Cast
    // ====

    protected:
    template <typename Derived>
    static std::exception& cast(Derived& derived) {
        return static_cast<std::exception&>(derived);
    }

    private:
    std::exception* m_exception;
};


// Runtime Errors
// ============================================================================

/// A Runtime Error.
class ErrorRuntime : public std::runtime_error, public Error
{
    public:
    explicit ErrorRuntime(const std::string& msg)
    :   std::runtime_error(msg), Error(cast(*this))
    {}
};

// Test
// ====

#include <iostream>

int main()
{
    try {
        throw ErrorRuntime("Hello Exception");
    }
    catch(const std::exception& e) {
        try {
            std::cerr << "std::exception: " << e.what() << "\n";
            throw;
        }
        catch(const Error& e) {
            try {
                // No what here
                std::cerr << "Error: " << e.msg() << "\n";
                throw;
            }
            catch(const ErrorRuntime& e) {
                std::cerr << "ErrorRuntime: " << e.what() << "\n";
            }
        }
    }
}

但是,如果您有第三方库,您可能会隐藏该库的所有异常(正如其他人所提到的)。隐藏的成本是传入和传出异常的两层。

【讨论】:

    猜你喜欢
    • 2015-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多