当异常变量未定义时,通过引用捕获异常

4
当捕获异常时,标准指导是按值抛出,按引用捕获。据我所知,这是由于以下两个原因:
1. 如果异常是由于内存不足而引发的,我们不会调用可能终止程序的复制构造函数。 2. 如果异常是继承层次结构的一部分,我们可能会在异常上进行对象切片。
如果在catch块中没有定义异常名称,那么这些问题(实际上是第一点,如果我们没有变量名称,则切片不会成为问题)仍然有效吗?
例如:
catch(my_exception)
{ ... }

或者

catch(my_exception &)
{ ... }

在这种情况下,如果通过值捕获异常,程序是否仍然有可能终止?我的感觉是,在技术上仍然有可能。

注意:我之所以问这个问题,是因为我不得不审核某人在这种情况下放置了一个按值捕获的catch。正如问题中所示,我并不确定任何选择的技术影响,但我认为,在一致性方面,无论如何,在这种情况下通过引用进行捕获更好(在任何情况下通过引用进行捕获没有任何不利影响)。


3
尽管捕获 my_exception& 是标准操作,但我发现大多数情况下异常并未被修改,因此最好是捕获 my_exception const&。请注意,whatconst 的。 - Matthieu M.
这个问题启发了一个关于在这种情况下复制省略的问题:https://dev59.com/x1vUa4cB1Zd3GeqPrThg - Potatoswatter
3个回答

7
标准不要求在未命名异常对象的情况下进行特殊优化。相反,它要求像复制初始化临时对象一样产生效果。这种复制可能导致动态分配内存。
N3290 §15.3/16: 如果exception-declaration指定了名称,则声明一个变量,该变量从异常对象进行复制初始化(8.5)。如果exception-declaration表示对象类型但未指定名称,则从异常对象复制初始化(8.5)一个临时对象(12.2)。变量或临时对象的生命周期在处理程序退出后结束,在处理程序中初始化任何自动对象的销毁之后。
上述段落没有提及按引用捕获,因此人们可以合理地得出结论,无论是否通过引用捕获异常对象,都适用于该段落;总之会构造一个副本。
然而,下一段话与此相矛盾:
N3290 §15.3/17: 当处理程序声明非常量对象时,对该对象所做的任何更改都不会影响通过throw-expression执行初始化的临时对象。当处理程序声明对非常量对象的引用时,对引用对象所做的任何更改都是对通过throw-expression执行初始化的临时对象的更改,并且应该对重新抛出该对象产生影响。
因此,声明类型T&(其中T为非const)是C++11要求直接引用抛出对象的唯一情况,而不是复制。在C++03中也是这样,只是C++03有关于as-if优化的额外措辞。因此,正式偏好应为:
    catch( T& name )

并且

    catch( T& )

然而,我总是像这样捕获异常:catch( T const& )。从实际角度来看,我们可以假设编译器将优化为对抛出对象的直接引用,即使可能存在设计情况,观察到的程序效果也不符合标准。例如:<evil grin>

#include <stdio.h>
#include <stdexcept>

struct Error
    : std::runtime_error
{
public:
    static Error* pThrown;

    char const* pMessage_;
    Error()
        : std::runtime_error( "Base class message" )
        , pMessage_( "Original message." )
    {
        printf( "Default-construction of Error object.\n" );
        pThrown = this;
    }

    Error( Error const& other )
        : std::runtime_error( other )
        , pMessage_( other.pMessage_ )
    {
        printf( "Copy-construction of Error obejct.\n" );
    }

    char const* what() const throw() { return pMessage_; }
};

Error*  Error::pThrown  = 0;

int main()
{
    printf( "Testing non-const ref:\n" );
    try
    {
        throw Error();
    }
    catch( Error& x )
    {
        Error::pThrown->pMessage_ = "Modified message.";
        printf( "%s\n", x.what() );
    }

    printf( "\n" );
    printf( "Testing const ref:\n" );
    try
    {
        throw Error();
    }
    catch( Error const& x )
    {
        Error::pThrown->pMessage_ = "Modified message";
        printf( "%s\n", x.what() );
    }
}

使用MinGW g++ 4.4.1和Visual C++ 10.0编译,输出结果为...

测试非const引用:
错误对象的默认构造。
修改消息。
测试const引用: 错误对象的默认构造。 修改消息。

一个拘泥于形式的人可能会说两个编译器都不符合规范,没有为Error const&情况创建副本。一个纯粹实践的从业者可能会说:嘿,你还能期望什么呢?而我认为标准中的措辞在这里非常不完美,如果有什么问题,应该明确允许上述输出,以便通过对const的引用进行捕获既安全又高效。

总结:

  • 通过引用捕获不会调用可能终止程序的复制构造函数。

  • 标准只保证对非const引用的情况。

  • 实际上,如上所示,即使在影响程序结果时,也保证了对const引用的情况。

祝好!


2

我更喜欢引用捕获。编译器可以将异常丢弃作为优化并不进行复制,但这只是一种可能性。通过引用捕获可以给您带来确定性。


1
问题:即使异常的复制构造函数具有副作用,编译器是否可以优化掉复制? - R. Martinho Fernandes

2
可能与您的异常相关联的是不可见的数据,例如vtable
vtable是一种不可见的数据结构,它携带有关特定多态(即虚拟)成员函数的位置信息。在一般情况下,该表会占用对象本身的一些内存。这可能是指向某个外部表的指针大小,甚至是整个表。就像往常一样,这取决于具体情况。

1
您能否详细说明在这种情况下可能意味着什么? - Christopher Howlin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接