【问题标题】:Custom Exceptions in C++C++ 中的自定义异常
【发布时间】:2010-12-09 00:17:56
【问题描述】:

我一直在尝试为我正在开发的 C++ 库创建一些自定义异常类。如果由于某种原因在测试异常时未在正确的位置捕获,这些自定义异常会捕获调试所需的额外信息,例如文件、行号等。然而,大多数人似乎建议从 STL 中的 std::exception 类继承,我同意这一点,但我想知道使用多重继承从 derived std::exception classes 中的每一个继承可能会更好(例如 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();
};

【问题讨论】:

  • 你会从中得到什么?为什么不使用单继承?为什么需要自定义 both Exception 和 RuntimeError 异常?
  • 我想使用自定义 Exception 类中的功能并通过从各种派生的 std::exception 类继承来维护 STL 异常层次结构。这样可以捕获:try{ throw RunTimeException(...); }catch(std::runtime_error &e){ ... }catch(std::exception &e){...} 也会捕获我的自定义类。但这可能是浪费时间。我也可以尝试{ throw RunTimeException(...); }catch(std::exception &e){...} 但它可能会使识别异常变得更加困难。不知道自定义异常类时的典型做法是什么?
  • 我会说只是从 std::runtime_error (或 std::exception)继承。如果你想要一个特定的治疗,你毕竟可以有多个捕获。
  • as @fmuecke points out creating a custom exception class that has the features you describe (adding arbitrary data to the exception) is difficult to get correct(尤其是在 C++ 中)。更容易使用像boost::exception 这样的代码,它经过了广泛的同行评审和实战测试。

标签: c++ stl exception multiple-inheritance


【解决方案1】:

你应该试试boost::exception

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

支持任意传输 数据到捕获站点,即 否则由于不投掷而变得棘手 例外要求 (15.5.1) 类型。数据可以添加到任何 异常对象,或者直接在 throw 表达式 (15.1),或在 稍后作为异常对象 向上传播调用堆栈。

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

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

【讨论】:

  • 我以前从未使用过 boost。我会调查的。感谢您的帖子。
【解决方案2】:

我想知道使用多重继承从每个派生的 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 库的异常类派生的。在该层次结构中,您定义了抽象异常类,以便您的用户可以捕获它们,以及从这些抽象类和您实际抛出的实现基类派生的相应实现类。为了使这更容易,我定义了一些可以完成所有艰苦工作的模板:

// 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 实例。但当时这不是必需的,所以我从来没有想过。

【讨论】:

  • 感谢您的帖子。制作具有多重继承的异常类是一个好习惯吗?还是仅仅从 std::exception 类继承更好?
  • 但是他没有这个问题。你已经偏离了与问题无关的切线。
  • @Martin:我不确定你在说什么。我什至在回答的基础上引用了我回答的问题。由于 Tom 在评论中提出问题,因此添加了编辑部分。
猜你喜欢
  • 1970-01-01
  • 2021-08-24
  • 1970-01-01
  • 2021-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-15
  • 2017-01-05
相关资源
最近更新 更多