2009-12-06 41 views
5

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

+0

几乎在任何地方都可以使用复制构造函数,C++标准允许使用它的用法。但是如果你希望你的代码是可移植的,那么异常就必须有一个可公开访问的拷贝构造函数。 – 2009-12-06 16:57:17

+0

此外,我不知道你使用的是哪一个版本的VC++,但是你肯定需要一个拷贝构造函数 - 我刚刚测试过它。 – 2009-12-06 17:01:20

回答

6

我会避免设计一个不同于您的库的异常层次结构。尽可能使用std::exception层次结构,总是从该层次结构中的某些内容中派生出您的异常。您可能需要特别阅读exceptions portion of Marshall Cline's C++ FAQ - 读取FAQ 17.6,17.9,17.1017.12

至于“强制用户参考搭上”,我不知道这样做的一个很好的方式。我已经在一个小时内拿出左右的打(这是周日下午)的唯一途径是基于polymorphic throwing

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: error: ‘bar_exception::bar_exception(const bar_exception&)’ is protected

foo.cpp:103: error: within this context

当然这一切是有代价的,因为你可以不再使用throw明示或您将看到一个类似的编译器警告打招呼:

foo.cpp: In function ‘void h()’:

foo.cpp:31: error: ‘foo_exception::foo_exception(const foo_exception&)’ is protected

foo.cpp:93: error: within this context

foo.cpp:31: error: ‘foo_exception::foo_exception(const foo_exception&)’ is protected

foo.cpp:93: error: within this context

总之,我会依靠编码标准和文件说明你应该总是引用。确保您的库捕获通过引用处理的异常并抛出新对象(例如,throw Class(constructorArgs)throw;)。我希望其他C++程序员能够拥有相同的知识 - 但为了确保可以在任何文档中添加注释。

+0

感谢您的时间!在库中的所有抛出都是通过一个宏来完成的,从而能够将文件名和行号编码为异常,所以我可以改变宏定义来调用raise。 – shojtsy 2009-12-06 19:21:27

15

你的异常需要有一个公共的拷贝构造函数。编译器必须能够复制它以便异常处理工作。

你的问题的解决方案是总是通过引用而不是追赶:

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

const -ness是可选的,但我一直把它放在因为有很少需要修改的异常对象。)

+3

另外:总是抛出新创建的异常(或者至少是你知道它们是如何创建的异常)。不要通过一个取消引用的指针或引用,这可能是派生类。 – 2009-12-06 16:18:19

+0

好点。我更新了这个例子来举一个“好”的例子。 – Thomas 2009-12-06 16:34:04

+0

我明白,例外情况应该引用。这正是我想通过隐藏复制构造函数来执行的。您的回答似乎表明图书馆作者无法强制在客户端代码中捕获异常的正确方法 – shojtsy 2009-12-06 16:59:41

9

托马斯的回答是正确的,但我也想建议你不要“设计一个异常层次结构”来浪费你的时间。设计类层次结构是一个非常糟糕的想法,尤其是当您可以简单地从C++ Standard异常类中派生一对(并且不超过)新的异常类型时。

+2

+1。我在这里和尼尔在一起。如果你认为你的异常比其他异常更频繁发生,因此需要它自己的异常类型(你可能有一个错误情况不是例外)。否则每个功能单元有1个异常(你如何定义功能单元是模糊的,但是比较大而不是较小)。 – 2009-12-06 17:06:18

-3

我会说不使用任何内置的C++异常代码。如果你必须有例外,从头开始创建你自己的。这是确保他们的行为类似的唯一方法,更不用说以类似的方式实施了,并且直言不讳地在C++中实现异常是无能的。

0

我已经找到阻止我库的客户从值不正确,捕获异常的两个便携式的方式从内部异常类virtual raise方法

  1. 抛出异常,并进行拷贝构造函数的保护。 (谢谢D.Shawley)
  2. 从库中抛出派生异常并为客户端发布异常基类。基类可以拥有受保护的拷贝构造函数,这只允许捕获它们的好方法。 (提到here为相似的问题)

C++标准确实表明复制构造函数需要在throw点可访问。我的配置中的Visual C++ 8.0违反了这部分标准,因为没有强制执行复制构造函数。在第15.1.3:

A throw-expression initializes a temporary object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively.

If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2), then the exception in the handler can be initialized directly with the argument of the throw expression. When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated)

这个答案被张贴到OP的问题,我从问题删除,并张贴作为一个单独的答案。

+0

虽然图书馆作者明智地让图书馆用户难以做错事 - 但不要颠覆标准也很重要。该标准规定异常对象必须是可复制的(15.1.5),并且它明确允许按值捕获。我会犹豫不决。 – 2014-11-27 14:39:07