17

我一直在尝试为我正在研究的 C++ 库创建一些自定义异常类。如果由于某种原因在测试异常时未在正确的位置捕获,这些自定义异常会捕获调试所需的额外信息,例如文件、行号等。然而,大多数人似乎建议从 STL 中的 std::exception 类继承,我同意这一点,但我想知道使用多重继承从每个派生的 std::exception 类继承可能会更好(例如.std::runtime_error) 和自定义异常类,如下面的代码所示?

另一件事,如何处理异常类中的复制构造函数和赋值运算符?他们应该被禁用吗?

class Exception{
    public:
        explicit Exception(const char *origin, const char *file, 
                           const int line, const char *reason="", 
                           const int errno=0) throw();

        virtual ~Exception() throw();

        virtual const char* PrintException(void) throw();

        virtual int GetErrno(void);

    protected:
        std::string m_origin;
        std::string m_file;
        int m_line;
        std::string m_reason;
        int m_errno;
}; 

class RuntimeError: public virtual std::runtime_error, public Exception{
    public:
              explicit RuntimeError(const char *origin, const char *file, 
                                    const int line, const char *reason="", 
                                    const int errno=0) throw();
        virtual ~RuntimeError() throw();
};
4

2 回答 2

17

我想知道也许使用多重继承从每个派生的 std::exception 类继承会更好

请注意,这是一个问题,因为标准库中的异常彼此非虚拟派生。如果您引入多重继承,您将获得可怕的菱形异常层次结构,而无需虚拟继承,并且无法通过 捕获派生异常std::exception&,因为您的派生异常类带有 的两个子对象std::exception,从而形成std::exception一个“模棱两可的基类”。

具体例子:

class my_exception : virtual public std::exception {
  // ...
};

class my_runtime_error : virtual public my_exception
                       , virtual public std::runtime_error {
  // ...
};

现在my_runtime_error(间接)从std::exception两次,一次通过std::run_time_error和一次通过my_exception。由于前者不是从std::exception虚拟中派生的,因此这

try {
  throw my_runtime_error(/*...*/);
} catch( const std::exception& x) {
  // ...
}

不会工作。

编辑:

我想我已经在 Stroustrup 的一本书中看到了涉及 MI 的异常类层次结构的第一个示例,因此我得出结论,总的来说,这是一个好主意。我认为 std lib 的异常实际上并不是相互衍生的。

当我上次设计异常层次结构时,我非常广泛地使用了 MI,但不是从 std lib 的异常类派生的。在那个层次结构中,有您定义的抽象异常类,以便您的用户可以捕获它们,以及从这些抽象类和您实际抛出的实现基类派生的相应实现类。为了使这更容易,我定义了一些可以完成所有艰苦工作的模板:

// something.h
class some_class {
private:
  DEFINE_TAG(my_error1); // these basically define empty structs that are needed to 
  DEFINE_TAG(my_error2); // distinguish otherwise identical instances of the exception 
  DEFINE_TAG(my_error3); // templates from each other (see below)
public:
  typedef exc_interface<my_error1>  exc_my_error1;
  typedef exc_interface<my_error2>  exc_my_error2;
  typedef exc_interface<my_error3,my_error2> // derives from the latter
                                    exc_my_error3;

  some_class(int i);
  // ...
};

//something.cpp
namespace {
  typedef exc_impl<exc_my_error1> exc_impl_my_error1;
  typedef exc_impl<exc_my_error2> exc_impl_my_error2;
  typedef exc_impl<exc_my_error3> exc_impl_my_error3;
  typedef exc_impl<exc_my_error1,exc_my_error2> // implements both
                                  exc_impl_my_error12;
}
some_class::some_class(int i)
{
  if(i < 0) 
    throw exc_impl_my_error3( EXC_INFO  // passes '__FILE__', '__LINE__' etc.
                            , /* ... */ // more info on error
                            ); 
}

现在回过头来看,我认为我可以使exc_impl该类模板派生自std::exception(或 std lib 异常层次结构中的任何其他类,作为可选模板参数传递),因为它从不派生自任何其他exc_impl实例。但当时这不是必需的,所以我从来没有想过。

于 2009-10-22T08:46:56.950 回答
12

你应该试试boost::exception

Boost Exception 的目的是简化异常类层次结构的设计并帮助编写异常处理和错误报告代码。

它支持将任意数据传输到捕获站点,否则由于异常类型的无抛出要求(15.5.1),这很棘手。可以将数据添加到任何异常对象,可以直接在 throw 表达式 (15.1) 中添加,也可以稍后在异常对象向上传播调用堆栈时添加。

在异常对象被传递给 throw 后向异常对象添加数据的能力很重要,因为处理异常所需的某些信息通常在检测到故障的上下文中不可用。

Boost Exception 还支持 N2179 样式的异常对象复制,由 boost::throw_exception 函数以非侵入方式自动实现。

于 2009-10-22T08:37:05.437 回答