我正在为我的库设计C++的异常层次结构。 “层次结构”是派生自std :: runtime_error的4个类。我想避免slicing problem的异常类,使复制构造函数受到保护。但显然gcc需要在抛出它们的实例时调用拷贝构造函数,因此抱怨受保护的拷贝构造函数。 Visual C++ 8.0编译相同的代码很好。是否有任何可移植的方法来缓解异常类的切片问题?该标准是否说明了某个实现是否可能需要要抛出的类的拷贝构造函数?避免切片异常类型(C++)
回答
我会避免设计一个不同于您的库的异常层次结构。尽可能使用std::exception
层次结构,总是从该层次结构中的某些内容中派生出您的异常。您可能需要特别阅读exceptions portion of Marshall Cline's C++ FAQ - 读取FAQ 17.6,17.9,17.10和17.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++程序员能够拥有相同的知识 - 但为了确保可以在任何文档中添加注释。
感谢您的时间!在库中的所有抛出都是通过一个宏来完成的,从而能够将文件名和行号编码为异常,所以我可以改变宏定义来调用raise。 – shojtsy 2009-12-06 19:21:27
你的异常需要有一个公共的拷贝构造函数。编译器必须能够复制它以便异常处理工作。
你的问题的解决方案是总是通过引用而不是追赶:
try {
// some code...
throw MyException("lp0 is on fire!");
} catch (MyException const &ex) {
// handle exception
}
(const
-ness是可选的,但我一直把它放在因为有很少需要修改的异常对象。)
托马斯的回答是正确的,但我也想建议你不要“设计一个异常层次结构”来浪费你的时间。设计类层次结构是一个非常糟糕的想法,尤其是当您可以简单地从C++ Standard异常类中派生一对(并且不超过)新的异常类型时。
+1。我在这里和尼尔在一起。如果你认为你的异常比其他异常更频繁发生,因此需要它自己的异常类型(你可能有一个错误情况不是例外)。否则每个功能单元有1个异常(你如何定义功能单元是模糊的,但是比较大而不是较小)。 – 2009-12-06 17:06:18
我会说不使用任何内置的C++异常代码。如果你必须有例外,从头开始创建你自己的。这是确保他们的行为类似的唯一方法,更不用说以类似的方式实施了,并且直言不讳地在C++中实现异常是无能的。
我已经找到阻止我库的客户从值不正确,捕获异常的两个便携式的方式从内部异常类virtual raise方法
- 抛出异常,并进行拷贝构造函数的保护。 (谢谢D.Shawley)
- 从库中抛出派生异常并为客户端发布异常基类。基类可以拥有受保护的拷贝构造函数,这只允许捕获它们的好方法。 (提到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的问题,我从问题删除,并张贴作为一个单独的答案。
虽然图书馆作者明智地让图书馆用户难以做错事 - 但不要颠覆标准也很重要。该标准规定异常对象必须是可复制的(15.1.5),并且它明确允许按值捕获。我会犹豫不决。 – 2014-11-27 14:39:07
- 1. 避免类型切换
- 2. C#\避免内存异常
- 3. 避免异常?
- 4. 避免checked异常
- 5. 如何避免TypeInitializer异常?
- 6. 与流避免NoSuchElementException异常
- 7. 避免NullPointerException异常检查
- 8. 避免在C++类
- 9. 避免对象切片并使用shared_ptr
- 10. 避免Python风格切片的缺陷
- 11. 避免类型铸造
- 12. 避免子类型选择
- 13. 避免Java类型擦除
- 14. 写入空文件时避免异常
- 15. 如何避免异常EXC_RESOURCE - WAKEUPS?
- 16. PMD中不可避免的DD异常
- 17. 如何避免这种异常?
- 18. 如何避免超时异常?
- 19. 避免抛出一个新的异常
- 20. 如何避免“Unobserved Task”异常?
- 21. 在Java中8避免NoSuchElementException异常流
- 22. 好的方法来避免DivideByZero异常
- 23. 避免与分页的OOM异常
- 24. 如何处理或避免BlockedIndefinitelyOnSTM异常?
- 25. 如何避免重复键异常
- 26. 如何避免罗马异常
- 27. 表达式评估 - 避免StackOverflow异常
- 28. 如何避免重复异常处理?
- 29. 如何避免ActionController :: InvalidCrossOriginRequest异常?
- 30. 避免来自PropertyGrid的异常
几乎在任何地方都可以使用复制构造函数,C++标准允许使用它的用法。但是如果你希望你的代码是可移植的,那么异常就必须有一个可公开访问的拷贝构造函数。 – 2009-12-06 16:57:17
此外,我不知道你使用的是哪一个版本的VC++,但是你肯定需要一个拷贝构造函数 - 我刚刚测试过它。 – 2009-12-06 17:01:20