通过 const 引用捕获异常并丢弃 const
Catch exception by const reference and cast away const
作为异常处理程序是否有效,其中 T
是一些 class 具有非 const
成员函数 func
?
换句话说:catch
是否保证直接绑定到(可修改的)异常对象,或者当您通过 const 引用捕获时,编译器是否有余地做一些诡计?
catch(const T &t)
{
const_cast<T &>(t).func();
}
我在标准中看不到任何 "trickery" 的证据。无法证明是否定的,但我相信你是 "safe"。 const
ness 在形式上似乎等同于此:
T obj;
const T& t = obj;
const_cast<T &>(t).func();
也就是说,const
ness 首先出现在 catch
块中存在的引用上,就是这样。
但这一切确实引出了一个问题:如果你不能通过观察来确定,那为什么还要去做呢?
一定要抓住一个T&
。
如其所说here:
If the parameter of the catch-clause is a reference type, any changes made to it are reflected in the exception object, and can be observed by another handler if the exception is rethrown with throw;
所以这表明修改异常对象是安全的,并且可以观察到更改。
但是,应该考虑仅通过普通引用进行捕获。或者,如果函数只能对标记为 mutable
的成员起作用,则将其设置为 const
。
任何这些听起来都不可读,并且会使合作开发者产生敌意。
(我正在使用 C++11;我需要更新;))
15.1/3(强调我的):
A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw
这意味着异常对象永远不会"born const",因此不应触发未定义的行为来修改。
来自[except.throw]:
Evaluating a throw-expression with an operand throws an exception (15.1); the type of the exception object
is determined by removing any top-level cv-qualifiers from the static type of the operand and adjusting the
type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”,
respectively.
并且,强调我的:
Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object. The
temporary is an lvalue and is used to initialize the variable declared in the matching handler (15.3).
因此,如果我们抛出一个 cv T 类型的操作数,我们将复制初始化一个 T 类型的临时对象。
然后根据[except.handle],如果:
,const T&(对于非指针类型 T)的处理程序匹配 E 类型的异常对象
- [...] E and T are the same type (ignoring the top-level cv-qualifiers),
- [...] T is an unambiguous public base class of E
此处理程序由以下人员初始化:
The variable declared by the exception-declaration, of type cv T or cv T&, is initialized from the exception
object, of type E, as follows:
— if T is a base class of E, the variable is copy-initialized (8.5) from the corresponding base class subobject
of the exception object;
— otherwise, the variable is copy-initialized (8.5) from the exception object.
因此,如果我们通过 const T& 进行捕获,我们将复制初始化来自异常对象的引用 - 我们从上一节中知道该异常对象将是类型 T 或从 T 公开派生。来自 [dcl.init.ref]:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
— If the reference is an lvalue reference and the initializer expression
— is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2”, or [...]
then the reference is bound to the initializer expression lvalue in the first case
关键是临时异常对象还是一个左值。因此,如果我们的处理程序与 const T& 匹配,我们知道引用直接绑定到类型 T 或 D 的对象(其中 D 派生自 T)——无论哪种方式,它都是与 [=11 引用兼容的类型=].因此,没有未定义的行为。如果临时对象是一个右值或者处理程序可以匹配更广泛的类型,那么将创建一个 const T
类型的临时对象 - 而你的 const_cast
肯定是未定义的行为。
虽然您的代码在符合标准的编译器上没有表现出未定义的行为,但确实没有理由不这样做:
catch(T &t)
{
t.func();
}
作为异常处理程序是否有效,其中 T
是一些 class 具有非 const
成员函数 func
?
换句话说:catch
是否保证直接绑定到(可修改的)异常对象,或者当您通过 const 引用捕获时,编译器是否有余地做一些诡计?
catch(const T &t)
{
const_cast<T &>(t).func();
}
我在标准中看不到任何 "trickery" 的证据。无法证明是否定的,但我相信你是 "safe"。 const
ness 在形式上似乎等同于此:
T obj;
const T& t = obj;
const_cast<T &>(t).func();
也就是说,const
ness 首先出现在 catch
块中存在的引用上,就是这样。
但这一切确实引出了一个问题:如果你不能通过观察来确定,那为什么还要去做呢?
一定要抓住一个T&
。
如其所说here:
If the parameter of the catch-clause is a reference type, any changes made to it are reflected in the exception object, and can be observed by another handler if the exception is rethrown with throw;
所以这表明修改异常对象是安全的,并且可以观察到更改。
但是,应该考虑仅通过普通引用进行捕获。或者,如果函数只能对标记为 mutable
的成员起作用,则将其设置为 const
。
任何这些听起来都不可读,并且会使合作开发者产生敌意。
(我正在使用 C++11;我需要更新;))
15.1/3(强调我的):
A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw
这意味着异常对象永远不会"born const",因此不应触发未定义的行为来修改。
来自[except.throw]:
Evaluating a throw-expression with an operand throws an exception (15.1); the type of the exception object is determined by removing any top-level cv-qualifiers from the static type of the operand and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively.
并且,强调我的:
Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable declared in the matching handler (15.3).
因此,如果我们抛出一个 cv T 类型的操作数,我们将复制初始化一个 T 类型的临时对象。
然后根据[except.handle],如果:
,const T&(对于非指针类型 T)的处理程序匹配 E 类型的异常对象
- [...] E and T are the same type (ignoring the top-level cv-qualifiers),
- [...] T is an unambiguous public base class of E
此处理程序由以下人员初始化:
The variable declared by the exception-declaration, of type cv T or cv T&, is initialized from the exception object, of type E, as follows:
— if T is a base class of E, the variable is copy-initialized (8.5) from the corresponding base class subobject of the exception object;
— otherwise, the variable is copy-initialized (8.5) from the exception object.
因此,如果我们通过 const T& 进行捕获,我们将复制初始化来自异常对象的引用 - 我们从上一节中知道该异常对象将是类型 T 或从 T 公开派生。来自 [dcl.init.ref]:
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
— If the reference is an lvalue reference and the initializer expression
— is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2”, or [...]then the reference is bound to the initializer expression lvalue in the first case
关键是临时异常对象还是一个左值。因此,如果我们的处理程序与 const T& 匹配,我们知道引用直接绑定到类型 T 或 D 的对象(其中 D 派生自 T)——无论哪种方式,它都是与 [=11 引用兼容的类型=].因此,没有未定义的行为。如果临时对象是一个右值或者处理程序可以匹配更广泛的类型,那么将创建一个 const T
类型的临时对象 - 而你的 const_cast
肯定是未定义的行为。
虽然您的代码在符合标准的编译器上没有表现出未定义的行为,但确实没有理由不这样做:
catch(T &t)
{
t.func();
}