为什么要抛出引用调用复制构造函数的异常?

Why throwing exception which is a reference calls copy constructor?

本文关键字:构造函数 异常 复制 调用 引用 为什么      更新时间:2023-10-16

为什么要抛出引用调用复制构造函数的异常?

struct Error
{
Error() {}
Error(const Error&) = delete;
};
int main()
{
Error& error = *new Error;
throw error;
}

编译错误:

error: declared here
Error(const Error&) = delete;

投掷指针时不会发生这种情况,例如:

int main()
{
Error* error = new Error;
throw error;
}

这没关系。

不能抛出引用。抛出始终将抛出的表达式值复制到为抛出的对象预留的特殊存储区域中。否则,你几乎总是会"捕获"一个悬而未决的引用,就像你的代码中[理论上]的情况一样。

您的Error类型无法复制,因此该程序是不可能的。

但是,指针当然可以复制,最后一个示例中的主要问题是内存泄漏。此外,您的程序将简单地在throw语句处终止,因为您没有任何try/catch

在展开堆栈之前,抛出运算符(抛出除外;没有参数,用于重新抛出)创建一个异常对象(在特殊的内存区域中)。根据情况,对象以不同的方式初始化:构造函数、复制构造函数、移动构造函数 (https://en.cppreference.com/w/cpp/language/copy_elision) 使用提供给 throw 运算符的内容。提供参考是可以的,但有三个:

  • 如果您在参数列表中提供参考,这取决于接收方、实际收到的内容、参考或价值副本;
  • 编译器需要初始化一个异常对象,因为当异常处理 catch 块运行时,提供给 throw 运算符的内容将不会存在(然后堆栈将被展开;如果是指针,则提供指针,尽管它指向的对象在您的情况下是活动的,并且在 catch 块中您有指向同一对象的指针的副本);
  • 无法
  • 在运行时初始化引用; 因此,在您的情况下,编译器需要复制构造函数,以便使用您提供的引用初始化异常对象(复制构造函数通常使用对初始对象的引用来初始化对象)。

当您将对 Error 的引用传递给 throw 运算符时,异常对象的类型为 Error,我们需要在该特定内存区域中初始化一个 Error 异常对象。

将指向 Error 的指针传递给抛出运算符时,异常对象的类型是指向 Error (Error *) 的指针,因此将复制指针,而不是指针指向的 Error 对象。指向 error 的复制指针与调用 Error 的复制构造函数无关,因此在这种情况下您没有错误。