视觉C++:引发异常会调用复制构造函数

visual C++: Throwing an exception invokes the copy constructor?

本文关键字:调用 复制 构造函数 异常 C++ 视觉      更新时间:2023-10-16

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

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;
}

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

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

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

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

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

它没有,但重新抛出抛出的异常是用 throw; 完成的。当您执行throw e;时,您请求抛出捕获的异常的副本。

几点:

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

第二种写的时候:

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

您实际上创建了一个新的异常,例如如果异常被扔了是foo的子类,他们我的召唤throw e你会失去这个信息,并实际使用复制构造函数从 e 生成foo - 无论什么是的。

现在当你打电话

catch(foo const &e) {
   throw;
}

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

所以:切勿使用throw e;向前传播异常,使用throw;

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

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

当 catch 块使用 throw; 而不是 throw e; 时,它可以重用之前创建的副本,而无需创建另一个副本。

看来你误解了重新抛出的含义。 当你这样做时——

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)

语言规范允许编译器根据需要创建任意数量的原始对象副本。份数没有限制。

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