1

我们有一个自定义错误类,每当我们抛出异常时都会使用它:

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,因为没有定义复制构造函数。

我在编译定义并首先抛出 a 的库时第一次看到错误CCLAError,这就是为什么我要继续进行,好像这是原因。

我能够通过更改来解决错误

throw CCLAError( ... )

throw new CCLAError( ... )

catch(CCLAError& e)
{
   throw e;
}

catch(CCLAError& e)
{
   throw;
}

但是,我不明白为什么重新抛出捕获的异常会调用复制构造函数。我错过了一些完全明显的东西吗?随后,为什么删除包含对捕获的异常的引用的变量会导致复制构造函数不被调用?

4

6 回答 6

10

抛出的对象的类型必须是throw可复制的,因为表达式可能会复制其参数(副本可能会被省略,或者在 C++11 中可能会发生移动,但复制构造函数必须仍然是可访问和可调用的)。

使用重新抛出异常throw;不会创建任何副本。使用抛出捕获的异常对象throw e;将导致创建副本e。这与重新抛出异常不同。

您的“更新”代码无法按预期工作。 catch (CCLAError&)不会捕获 类型的异常CCLAError*,该类型是由 引发的异常的类型throw new CCLAError(...);。你需要抓住CCLAError*. 但是,不要这样做。按值抛出异常并按引用捕获。所有异常类型都应该是可复制的。

于 2012-06-01T18:29:48.197 回答
5

但是,我不明白为什么重新抛出捕获的异常会调用复制构造函数。

它没有,但重新抛出抛出的异常是用throw;. 当您这样做时,throw e;您要求抛出捕获的异常的副本。

于 2012-06-01T18:30:07.260 回答
3

几点:

首先不要调用throw new foo()使用throw foo

第二当你写:

catch(foo const &e) {
   throw e;
}

您实际上创建了一个新异常,例如,如果抛出的异常是foo我调用的子类,throw e您将丢失此信息并实际使用复制构造函数foo从 e 生成 - 不管它是什么。

现在当你打电话

catch(foo const &e) {
   throw;
}

您不会创建新异常,而是传播相同的异常。

所以:永远不要使用throw e;向前传播异常,使用throw;

于 2012-06-01T18:31:34.170 回答
1

当您抛出异常时,您要抛出的对象通常驻留在堆栈中。作为抛出过程的一部分,堆栈正在被清理,因此编译器必须制作一个可以在该点之后继续存在的副本。

当您使用 抛出一个对象时new,您不是在抛出实际的对象,而是在抛出一个指向该对象的指针。这意味着您的catch块还必须捕获指针而不是引用。不要忘记delete指向指针,否则会发生内存泄漏!

当 catch 块使用throw;而不是它时,throw e;它可以重用它之前制作的副本,并且不需要制作另一个副本。

于 2012-06-01T18:37:15.670 回答
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)
于 2012-06-01T18:38:20.790 回答
-1

语言规范允许编译器根据需要制作尽可能多的原始对象副本。份数没有限制。

这意味着,您的自定义异常类必须具有可访问的复制构造函数,可以是编译器生成的,也可以是用户定义的。

于 2012-06-01T18:30:11.537 回答