我们有一个自定义错误类,每当我们抛出异常时都会使用它:
class AFX_CLASS_EXPORT CCLAError : public CObject
它定义了以下复制构造函数:
CCLAError(const CCLAError& src) { AssignCopy(&src); } // (AssignCopy is a custom function)
它最初是用MSVC6(Visual Studio 2003)编写和编译/链接的。我正在进行必要的更改以使其编译并链接到MSVC8 +(VS 2008 +)
调用msvc8链接器时,出现以下错误:
LNK2001: unresolved external symbol "private: __thiscall CObject::CObject(class CObject const &)" (??0CObject@@AAE@ABV0@@Z)
我理解错误告诉我的内容:没有为CObject的某个子节点定义复制构造函数,所以它一直向上继承树,直到它到达CObject,而没有复制构造函数定义。
在编译定义并首先抛出CCLAError
的库时,我首先看到了错误,这就是我正在进行的原因,就好像这是原因。
我可以通过更改
来解决错误throw CCLAError( ... )
到
throw new CCLAError( ... )
和
catch(CCLAError& e)
{
throw e;
}
到
catch(CCLAError& e)
{
throw;
}
但是,我不明白为什么重新抛出一个被捕获的异常会调用复制构造函数。我错过了一些完全明显的东西吗?随后,为什么删除持有对捕获的异常的引用的变量会导致不调用复制构造函数?
答案 0 :(得分:9)
抛出的对象的类型必须是可复制的,因为throw
表达式可以复制其参数(复制可能被省略,或者在C ++ 11中可能会发生移动,但是复制构造函数必须仍然可以访问和调用。)
使用throw;
重新排除异常不会创建任何副本。使用throw e;
抛出捕获的异常对象将导致生成e
的副本。这与重新抛出异常不同。
您的“更新”代码无法按预期运行。 catch (CCLAError&)
不会捕获类型CCLAError*
的异常,这是throw new CCLAError(...);
抛出的异常的类型。你需要抓住CCLAError*
。但是,不要这样做。按值抛出异常并通过引用捕获。所有异常类型都应该是可复制的。
答案 1 :(得分:5)
但是,我不明白为什么重新抛出一个被捕获的异常会调用拷贝构造函数。
它没有,但是使用throw;
重新抛出抛出的异常。执行throw e;
时,您要求提供已捕获异常的副本。
答案 2 :(得分:3)
有几点:
首先不要致电throw new foo()
使用throw foo
第二次写作时:
catch(foo const &e) {
throw e;
}
您实际创建了一个新的异常,例如,如果是异常的话
被抛出的是foo
的子类,我的调用throw e
你会松开它
信息并实际使用复制构造函数从e - 生成foo
它是。
现在打电话
catch(foo const &e) {
throw;
}
您不会创建新的异常,而是传播相同的异常。
所以:从不使用throw e;
向前传播异常,使用throw;
答案 3 :(得分:1)
当你抛出异常时,你抛出的对象通常驻留在堆栈上。作为抛出过程的一部分,堆栈正在被清理,因此编译器必须制作一个可以继续存在的副本。
当您使用new
抛出一个对象时,您不会抛出实际对象,而是抛出指向该对象的指针。这意味着您的catch
块也必须捕获指针而不是引用。不要忘记delete
指针,否则你会有内存泄漏!
当catch块使用throw;
代替throw e;
时,它可以重复使用之前制作的副本,而无需再制作副本。
答案 4 :(得分:0)
你似乎误解了重新投掷意味着什么。当你这样做 -
catch(CCLAError& e)
{
throw e;
}
- 你不是在重新抛弃。相反,正如您所观察到的,您确实正在创建新异常的副本。相反(再次,正如你自己找到的那样),这在技术上是重新抛出的正确方法:
catch(CCLAError& e)
{
throw;
}
阅读Stroustrup的TC ++ PL第14章(14.3.1讨论重新抛出)。
此外,您不必这样做 -
throw new CCLAError( ... )
相反,做 -
throw CCLAError( ... )
- 就像之前一样,只接受 CONST 引用(你不能持有对临时的引用)。
catch(const CCLAError &e)
答案 5 :(得分:-1)
语言规范允许编译器根据需要制作原始对象的多个副本。副本数量没有限制。
这意味着,您的自定义异常类必须具有可访问复制构造函数,无论是编译器生成的还是用户定义的。