【问题标题】:Catch exception by const reference and cast away const通过 const 引用捕获异常并丢弃 const
【发布时间】:2015-07-20 21:42:35
【问题描述】:

这作为异常处理程序是否有效,其中T 是具有非const 成员函数func 的某个类?

换句话说:catch 是否保证直接绑定到(可修改的)异常对象,或者当您通过 const 引用捕获时,编译器是否可以做一些诡计?

catch(const T &t)
{
    const_cast<T &>(t).func();
}

【问题讨论】:

  • 为什么yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy??
  • 因为马特显然是一名语言律师
  • @dufresnb:绝对没有理由编写这样的代码。
  • @LightnessRacesinOrbit 这样我就可以明确回应其他编写此代码的人
  • @MattMcNabb:哈哈好吧。仍然。答案是“你很安全但很愚蠢”,所以只要自信地回答,就这样吧。

标签: c++ exception language-lawyer


【解决方案1】:

来自[except.throw]:

使用操作数计算 throw 表达式会引发异常 (15.1);异常对象的类型 通过从操作数的静态类型中删除任何顶级 cv 限定符并调整 从“T 的数组”或“返回 T 的函数”键入“指向 T”或“指向返回 T 的函数的指针”, 分别。

并且,强调我的:

抛出异常会复制初始化(8.5、12.8)一个临时对象,称为异常对象。这 临时是一个左值,用于初始化匹配处理程序(15.3)中声明的变量。

因此,如果我们抛出一个 cv T 类型的操作数,我们将复制初始化一个 T 类型的临时对象。

然后根据[except.handle],const T&(用于非指针类型T)的处理程序匹配E类型的异常对象,如果:

  • [...] E 和 T 是同一类型(忽略顶级cv-qualifiers),
  • [...] T 是 E 的明确公共基类

这个处理程序被初始化:

由异常声明声明的变量,类型为 cv T 或 cv T&,从异常中初始化 对象,类型 E,如下:
— 如果 T 是 E 的基类,则变量从相应的基类子对象复制初始化(8.5) 异常对象的;
— 否则,变量会从异常对象复制初始化 (8.5)。

因此,如果我们通过 const T& 捕获,我们正在复制初始化来自异常对象的引用——我们从上一节中知道,它要么是 T 类型,要么是从 T 公开派生的。来自 [dcl.init.参考]:

对“cv1T1”类型的引用由“cv2T2”类型的表达式初始化,如下所示:
— 如果引用是左值引用和初始化表达式
— 是一个左值(但不是位域),并且“cv1 T1”与“cv2 T2”引用兼容,或者 [...]

那么在第一种情况下引用绑定到初始化表达式左值

关键是临时异常对象还是一个左值。因此,如果我们的处理程序与 const T& 匹配,我们知道该引用直接绑定到 T 或 D 类型的对象(其中 D 派生自 T) - 无论哪种方式,它都是与 const T 引用兼容的类型.因此,没有未定义的行为。如果临时对象是右值或处理程序可以匹配更广泛的类型,那么将创建一个类型为 const T 的临时对象 - 并且您的 const_cast 肯定是未定义的行为。

虽然您的代码在符合标准的编译器上没有表现出未定义的行为,但确实没有理由不这样做:

catch(T &t)
{
    t.func();
}

【讨论】:

    【解决方案2】:

    (我使用的是 C++11;我需要更新;))

    15.1/3(强调我的):

    throw-expression 初始化一个临时对象,称为异常对象,其类型是通过从静态类型中删除任何顶级 cv-qualifiers 来确定的throw的操作数

    这意味着异常对象永远不会“天生 const”,因此不应触发未定义的行为来修改。

    【讨论】:

    • 这看起来很有定论;不错。
    • FWIW 的措辞在 C++14 中是一样的
    • 从技术上讲,这并不能证明 ref 绑定到上面定义的对象并且没有“诡计”,但是,正如我自己的回答,你不能反驳否定。以及为什么 以这种方式工作呢?这个问题有点不清楚我们需要反驳什么诡计,所以......
    • 我的问题是catch 是否必须直接绑定到该对象。巴里(已删除)的答案开始进入 this 。通过复制初始化来初始化 const 引用有时会创建一个临时的。
    • @Barry:N4527 不是标准。有趣的是,这可能会在 C++17 中发生变化?有没有类似的写法?
    【解决方案3】:

    我没有看到标准中有任何“诡计”的证据。不能证明是负面的,但我相信你是“安全的”。 constness 在形式上似乎与此等效:

    T obj;
    const T& t = obj;
    const_cast<T &>(t).func();
    

    也就是说,constness 首先出现在存在于 catch 块中的引用上,就是这样。

    但这一切确实引出了一个问题:如果您无法通过查看来确定,那为什么要这样做?

    一定要抓住T&amp;

    【讨论】:

      【解决方案4】:

      正如它所说的here

      如果 catch 子句的参数是引用类型,对其所做的任何更改都会反映在异常对象中,并且如果使用 throw 重新抛出异常,则可以被另一个处理程序观察到;

      因此这表明修改异常对象是安全的,并且更改将是可观察的。

      但是,应该考虑仅通过普通引用进行捕获。或者,如果函数仅适用于标记为 mutable 的成员,则创建函数 const

      任何这些听起来都不可读,并且可能使共同开发者充满敌意。

      【讨论】:

      • 没有真正回答问题。
      • 这意味着如果您通过引用并重新抛出,则重新抛出的处理程序可以看到您的更改。这并不意味着修改const&amp; 拍摄的任何内容都可以。
      • @LightnessRacesinOrbit 问题是“保证直接绑定到(可修改的)异常对象的捕获”。引用表明它是。
      • @BillyONeal const T&amp; 是一个引用类型,所以引用似乎是相关的,即使不是直接引用。
      • @BartoszKP:哦,我想是的。如果您将该结论添加到答案中会更清楚。
      猜你喜欢
      • 2022-08-03
      • 1970-01-01
      • 1970-01-01
      • 2011-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多