在C ++的异常层次结构中引入一个额外的基类型?

时间:2015-01-07 16:57:47

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

我们的基本情况如下:

// 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与第三方库中的任何错误有关,但是只是抛出了使用来自第三方库的东西的相同上下文。

// 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

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

这看起来像是一个可行的解决方案吗?或者尝试将non-virtual-std::exception类型与上面的层次结构进行整合是一个“非常糟糕的主意”?

注意:我们可以改变第三方库以便从std :: exception中使用0.00%“正确”地派生出来,这是偶然的(如在virtual中)。


  • 当然,如果先前版本中的任何catch(ns3p::OperationException&)“意外地”抓住了InvalidXYZExecption,那么现在就会中断,但这是可以接受的。

3 个答案:

答案 0 :(得分:1)

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

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

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

因此,您应该通过捕获其中任何一个并以用户期望的方式重新抛出它们来从库的用户中抽象出来。

从您的库中抛出的异常与“函数”中的返回值一样,都是“契约”的一部分。

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

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

答案 1 :(得分:1)

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

处理程序是E类型的异常对象的匹配,如果...处理程序的类型为cv T或cv T&和T是一个明确的公共基类E或......

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

因此,只要您在ModuleBaseException之前抓住ns3p::OperationException,任何从ModuleBaseException派生的异常都应该被捕获到正确的处理程序中,即使它也来自ns3p::OperationException

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

答案 2 :(得分:1)

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

#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";
            }
        }
    }
}

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