定制异常层次结构来自std :: exception和std :: bad_alloc的可怕钻石

时间:2014-03-06 03:15:19

标签: c++ exception c++11 polymorphism multiple-inheritance

我在自己的异常层次结构中存在继承问题。

Exception具有非常好的功能(回溯,记录,...),因此它是我的任何异常的基类。正如我在许多网页中所建议的那样,它继承自std::exception。此外,我正在使用一个单元测试框架来报告任何std::exception意外抛出。但最后说,这只是为了方便。

然后,我有一个新的OutOfMemoryException类,它将由自定义的new_handler抛出。此类继承自Exception,但也继承自std::bad_alloc以与现有代码兼容。我猜这是更重要的,因为new不会再抛出std::bad_alloc

这里的问题很明显:由于std::bad_alloc派生自std::exception,我有一个可怕的钻石情况

class Exception : public std::exception { };
class OutOfMemoryException : public Exception, public std::bad_alloc { };

可悲的是,正如您在此处看到的那样stackoverflow.com...标准异常不是虚拟继承,因此std::exception是一个含糊不清的基类。

然后:

  1. 有可能以任何方式解决这个问题吗? (我不知道,任何课程或模板技巧)

  2. 如果不是,Exception最好继承std::exceptionOutOfMemoryException 来自std::bad_alloc

  3. 由于其许可许可,我可以破解单元测试框架。

    提前谢谢你,对不起,我的英语发言人不是很好。

3 个答案:

答案 0 :(得分:3)

有一种方法可以获得额外的异常功能,但无需创建另一个异常层次结构。即您可以使用额外的位来抛出标准异常:

struct MyExceptionInfo { /* your extra functionality goes here */};

template<class T>
struct Ex : T, MyExceptionInfo
{
    template<class A1, class... Args>
    Ex(A1&& a1, Args&&... args)
        : T(std::forward<A1>(a1))
        , MyExceptionInfo(std::forward<Args>(args)...)
    {}
};

int main() {
    try {
        throw Ex<std::runtime_error>("oops");
    }
    catch(std::exception &e) {
        if(MyExceptionInfo* my_info = dynamic_cast<MyExceptionInfo*>(&e)) {
            // use MyExceptionInfo here
        }
    }
}

您可能还想看看Boost.Exception图书馆的灵感。

答案 1 :(得分:2)

我想到的一件事是,当您从Exception继承时,可以使用Curiously recurring template patternbad_alloc派生OutOfMemoryException(以及您可能拥有的其他异常类)来自其他错误,例如RuntimeException from runtime_error`):

template<typename Base=std::exception>
class Exception: public Base {...};
class OutOfMemoryException: public Exception<std::bad_alloc> {...};
class RuntimeException: public Exception<std::runtime_error> {...};

当然这意味着Exception需要成为一个模板,如果您确实希望捕获Exception而不是std::exception来访问其功能,则会导致编译时间开销和问题(因为你现在需要实际抛出哪个异常,这不是最优的)。这可以通过使用另一个不从std::exception派生并包含实际功能的Baseclass来迁移:

class ExceptionBase {...};
template<typename Base=std::exception>
class Exception: public Base, public ExceptionBase {...};
class OutOfMemoryException: public Exception<std::bad_alloc> {...};
class RuntimeException: public Exception<std::runtime_error> {...};

由于失去了一些多态属性并且当你有多个继承级别时很容易变得复杂,这实际上不是一个很好的解决方案,但它至少可以工作。

在实现方面,另一个稍微复杂的解决方案是将整个事物重构为两个不同的继承链,使用虚拟类来组合两个链:

class ExceptionT {};
class OutOfMemoryT: public ExceptionT {};
class Exception:   public ExceptionT, public std::exception {};
class OutOfMemory: public OutOfMemoryT, public std::bad_alloc {};

这样您就会抛出OutOfMemory并抓住OutOfMemoryTstd::bad_alloc

这两种方法都使得从处理程序中的一个Exceptionchain(Exception/std::exception)转换到另一个异常链,但我认为这可能不那么重要(因为std::exception没有提供太多除了标准之外你还可能总是喜欢捕捉异常。

答案 2 :(得分:0)

并不是说这个解决方案很漂亮,但它可能足以满足您的需求。 假设std::exception定义了一个名为func1()的函数。您当然可以从继承中的两个路径获取它,并且对OutOfMemoryException::func1()的任何调用都会为您提供有关模糊基类的编译时消息。

所以这里是hacky和不那么漂亮的解决方案。覆盖std::exception的非虚函数,只转发任一版本。 Exception::func1()std::bad_alloc::func1()

我确实知道这很丑,因为你要覆盖非虚函数。如果覆盖函数接受参数,您还会遇到完美的转发问题。如果你不小心,可能会复制你不想复制的东西等等。但它应该允许你从std :: exception调用成员函数。