5

我正在为我的库设计 C++ 中的异常层次结构。“层次结构”是从 std::runtime_error 派生的 4 个类。我想避免异常类的切片问题,因此使复制构造函数受到保护。但显然 gcc 在抛出它们的实例时需要调用复制构造函数,因此抱怨受保护的复制构造函数。Visual C++ 8.0 可以很好地编译相同的代码。是否有任何可移植的方法来化解异常类的切片问题?标准是否说明了实现是否可以/应该需要要抛出的类的复制构造函数?

4

5 回答 5

15

您的异常需要有一个公共复制构造函数。编译器必须能够复制它才能进行异常处理。

您的问题的解决方案是始终通过引用来捕获:

try {
    // some code...
    throw MyException("lp0 is on fire!");
} catch (MyException const &ex) {
    // handle exception
}

const-ness 是可选的,但我总是把它放进去,因为很少需要修改异常对象。)

于 2009-12-06T16:10:22.910 回答
9

Thomas 的回答是正确的,但我还想建议您不要通过“设计异常层次结构”来浪费时间。设计类层次结构是一个非常糟糕的主意,特别是当您可以从 C++ 标准异常类中简单地派生几个(仅此而已)新异常类型时。

于 2009-12-06T16:35:45.097 回答
6

我会避免设计与您的库不同的异常层次结构。std::exception尽可能多地使用层次结构,并始终从该层次结构中的某些内容中获取异常。您可能想阅读 Marshall Cline 的 C++ FAQ 的异常部分-特别阅读FAQ 17.6、17.9、17.1017.12

至于“强制用户通过引用捕获”,我不知道有什么好的方法。我在一个小时左右的比赛中(周日下午)想出的唯一方法是基于多态投掷

class foo_exception {
public:
    explicit foo_exception(std::string msg_): m_msg(msg_) {}
    virtual ~foo_exception() {}
    virtual void raise() { throw *this; }
    virtual std::string const& msg() const { return m_msg; }
protected:
    foo_exception(foo_exception const& other): m_msg(other.m_msg) {}
private:
    std::string m_msg;
};

class bar_exception: public foo_exception {
public:
    explicit bar_exception(std::string msg_):
        foo_exception(msg_), m_error_number(errno) {}
    virtual void raise() { throw *this; }
    int error_number() const { return m_error_number; }
protected:
    bar_exception(bar_exception const& other):
        foo_exception(other), m_error_number(other.m_error_number) {}
private:
    int m_error_number;
};

这个想法是使复制构造函数受到保护并强制用户调用Class(args).raise()而不是throw Class(args). 这使您可以抛出一个多态绑定异常,您的用户只能通过引用捕获该异常。任何按值捕获的尝试都应该得到一个很好的编译器警告。就像是:

foo.cpp:59: 错误: 'bar_exception::bar_exception(const bar_exception&)' 受保护

foo.cpp:103:错误:在此上下文中

当然,这一切都是有代价的,因为您不能再显式使用throw,否则您会收到类似的编译器警告:

foo.cpp:在函数'void h()'中:

foo.cpp:31: 错误: 'foo_exception::foo_exception(const foo_exception&)' 受保护

foo.cpp:93:错误:在此上下文中

foo.cpp:31: 错误: 'foo_exception::foo_exception(const foo_exception&)' 受保护

foo.cpp:93:错误:在此上下文中

总的来说,我会依赖编码标准和文档说明你应该总是通过引用来了解。确保您的库捕获它通过引用处理的异常并抛出新对象(例如,throw Class(constructorArgs)throw;)。我希望其他 C++ 程序员也有同样的知识——但为了确定起见,请在任何文档中添加注释。

于 2009-12-06T17:36:47.890 回答
0

我发现阻止我的库的客户端按值错误捕获异常的两种可移植方法是

  1. 从异常类的虚拟 raise方法中抛出异常,并使复制构造函数受到保护。(感谢 D.Shawley)
  2. 从库中抛出派生异常并发布异常基类以供客户端捕获。基类可能具有受保护的复制构造函数,这仅允许捕获它们的好方法。(这里提到一个类似的问题)

C++ 标准确实声明复制构造函数需要在抛出点可访问。我的配置中的 Visual C++ 8.0 通过不强制复制构造函数的存在违反了这部分标准。在第 15.1.3 节中:

throw-expression 初始化一个临时对象,其类型通过从 throw 操作数的静态类型中删除任何顶级 cv 限定符并将类型从“T 数组”或“函数返回 T”调整为分别是“指向 T 的指针”或“指向返回 T 的函数的指针”。

如果除了执行与使用临时对象相关的构造函数和析构函数(12.2)之外,可以在不改变程序含义的情况下消除临时对象的使用,则可以直接使用参数初始化处理程序中的异常的 throw 表达式。当抛出的对象是一个类对象,并且用于初始化临时副本的复制构造函数不可访问时,程序是病态的(即使临时对象可以被消除)

该答案由 OP 发布到问题中,我将其从问题中删除并作为单独的答案发布。

于 2014-11-27T14:10:05.650 回答
-4

我想说不要使用任何内置的 C++ 异常代码。如果您必须有例外,请从头开始创建自己的例外。这是确保它们行为相似的唯一方法,更不用说以类似的方式实现了,并且直言不讳地,C++ 中异常的实现是不称职的。

于 2009-12-07T02:20:39.320 回答