标准不要求在未命名异常对象的情况下进行特殊优化。相反,它要求像复制初始化临时对象一样产生效果。这种复制可能导致动态分配内存。
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
的引用进行捕获既安全又高效。
总结:
祝好!
my_exception&
是标准操作,但我发现大多数情况下异常并未被修改,因此最好是捕获my_exception const&
。请注意,what
是const
的。 - Matthieu M.