如果引发的异常始终是异常对象的副本,为什么不调用此复制构造函数?

If a thrown exception is always a copy of the exception object, why isn't this copy constructor being invoked?

本文关键字:异常 调用 为什么不 复制 构造函数 对象 如果 副本      更新时间:2023-10-16

Scott Meyers说:

C++指定始终复制作为异常抛出的对象,并且复制由对象的复制构造函数执行。

但在我的代码中:

struct test
{
    test() { cout << "constructor is called" << endl; }
    test(const test&) { cout << "copy constructor is called" << endl; }
    ~test() { cout << "destructor is called" << endl; }
};
void fun()
{
    throw test();
}
int main()
{
    try { 
       fun();
    }
    catch (test& t1) { cout << "exception handler" << endl; }
}

我没有看到异常对象的复制构造函数被调用。

如果我更改catch以按值接收异常对象,那么它就是,但根据Meyers的引用,即使通过引用接收异常对象也应该复制它。

为什么不调用复制构造函数(即使通过引用执行异常处理)

Meyers在语义上认为复制是正确的:

[C++11: 12.2/1]:类类型的临时在各种上下文中创建:绑定对prvalue的引用(8.5.3),返回prvalue(6.6.3),创建prvalue的转换(4.1、5.2.9、5.2.11、5.4),抛出异常(15.1),输入处理程序(15.3),以及在某些初始化中(8.5)。[…]

[C++11: 15.1/4]:抛出异常的临时副本的内存以未指定的方式分配,3.7.3.1中指出的除外。只要有一个处理程序正在为该异常执行,临时就会持续存在。

然而,聪明的编译器可以消除副本,并且允许他们这样做,而不考虑副作用。

[C++11: 12.8/31]:当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制或移动构造函数和/或析构函数有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象在没有优化的情况下被销毁的较晚时间。这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以组合以消除多个副本):

  • [..]
  • 当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv不合格类型的类对象,通过将临时对象直接构建到被省略的复制/移动的目标中,可以省略复制/移动操作
  • [..]

由于C++11,当一个对象作为异常抛出时,复制到异常对象可能会被消除,如果复制消除的条件已经满足或将要满足,除了源是函数参数,但编译器由于某些原因没有消除它,编译器必须尝试使用move构造函数,即使对象是由左值指定的。