在 `typeid` 代码中奇怪地使用`?:`

Posted

技术标签:

【中文标题】在 `typeid` 代码中奇怪地使用`?:`【英文标题】:Weird use of `?:` in `typeid` code 【发布时间】:2011-10-11 08:57:27 【问题描述】:

在我正在进行的一个项目中,我看到了这段代码

struct Base 
  virtual ~Base()  
;

struct ClassX 
  bool isHoldingDerivedObj() const 
    return typeid(1 ? *m_basePtr : *m_basePtr) == typeid(Derived);
  
  Base *m_basePtr;
;

我从未见过typeid 被这样使用。为什么它会和?: 跳那种奇怪的舞蹈,而不是只跳typeid(*m_basePtr)?有什么理由吗? Base 是一个多态类(带有虚拟析构函数)。

编辑:在这段代码的另一个地方,我看到了这个,它似乎是“多余的”

template<typename T> T &nonnull(T &t)  return t; 

struct ClassY 
  bool isHoldingDerivedObj() const 
    return typeid(nonnull(*m_basePtr)) == typeid(Derived);
  
  Base *m_basePtr;
;

【问题讨论】:

没有那个你试过了吗? 会不会是偶然的遗产? (也许并不总是1 ? ... 问题是条件总是会计算为真,并且两个分支产生完全相同的值。你能看看版本控制的历史(如果有的话),看看它过去是否是别的东西吗? 我同意@pst:很可能是遗留问题。 这看起来像是一个非常聪明的方法来击败一些过度热心的编译器优化或货物崇拜编程。 【参考方案1】:

我认为这是一个优化typeid 的一个鲜为人知且很少(您可以说“从不”)使用的功能是 typeid 参数的 null 取消引用会引发异常,而不是通常的 UB。

什么?你是认真的吗?你喝醉了吗?

确实如此。是的。没有。

int *p = 0; *p; // UB 类型标识 (*p); // 抛出

是的,这很丑陋,即使按照 C++ 语言丑陋的标准来衡量也是如此。

OTOH,这在typeid 的参数内部 的任何地方都不起作用,因此添加任何混乱都会取消此“功能”:

int *p = 0; typeid(1 ? *p : *p); // UB typeid(身份(*p)); // UB

郑重声明:我并没有在此消息中声称编译器在执行取消引用之前自动检查指针是否为空一定是一件疯狂的事情。我只是说,当取消引用是typeid 的直接参数时进行此检查,而不是其他地方,这完全是疯狂的。 (也许是某个草稿中插入的恶作剧,从未被删除。)

声明:我在之前的“声明”中并没有声称编译器插入自动检查指针是否为空,并在出现异常时抛出异常(如在 Java 中)是有意义的。 null 被取消引用:通常,在 null 取消引用上抛出异常是荒谬的。这是一个编程错误,因此异常不会有帮助。调用断言失败。

【讨论】:

什么?你是认真的吗?你喝醉了吗? 很好的解释。我只希望每当我遇到这个“成语”时我能记住这一点;我可能不会(我很少看到或使用typeid)。 标准要求当typeid 表达式的参数是lvalue 时抛出异常。在这种情况下任意使用三元运算符不会改变这种行为,并且符合要求的编译器必须抛出。 @DavidRodríguez-dribeas 是最近的变化吗? @curiousguy “值类别”是表示表达式是否为左值等的术语。可能的类别是 lvalue, xvalue, prvalue 。还有组合 glvalue, rvalue 。【参考方案2】:

[expr.typeid]/2 (N3936) 涵盖了此行为:

typeid 应用于类型为多态类类型的glvalue 表达式时,结果引用一个std::type_info 对象,该对象表示glvalue 所针对的最派生对象(即动态类型)的类型指。如果通过将一元 * 运算符应用于指针来获得 glvalue 表达式,并且指针是空指针值,则 typeid 表达式将引发与 std::bad_typeid 异常类型的处理程序匹配的类型的异常。

表达式1 ? *p : *p 始终是一个左值。这是因为*p是一个左值,而[expr.cond]/4表示如果三元运算符的第二个和第三个操作数具有相同的类型和值类别,则该运算符的结果具有该类型和值类别也。

因此,1 ? *m_basePtr : *m_basePtr 是一个类型为Base左值。由于Base 有一个虚析构函数,它是一个多态类类型。

因此,这段代码确实是“当typeid应用于类型为多态类类型的glvalue表达式时”的示例。


现在我们可以阅读上面引用的其余部分了。 glvalue 表达式 not “通过将一元 * 运算符应用于指针获得” - 它是通过三元运算符获得的。因此,如果m_basePtr 为空,该标准不要求抛出异常。

m_basePtr 为 null 的情况下的行为将被关于取消引用空指针的更一般规则所涵盖(这实际上在 C++ 中有点模糊,但出于实际目的,我们假设它会导致未定义的行为在这里)。


最后:为什么有人会写这个?我认为好奇的人的回答是迄今为止最合理的建议:使用这种结构,编译器不必插入空指针测试和代码来生成例外,所以是微优化。

大概程序员要么很高兴,因为它永远不会被空指针调用,要么很高兴依赖特定实现对空指针取消引用的处理。

【讨论】:

【参考方案3】:

我怀疑某些编译器是,对于简单的情况

typeid(*m_basePtr)

返回 typeid(Base) 总是,无论运行时类型如何。但是将其转换为表达式/临时/右值会使编译器给出 RTTI。

问题是哪个编译器,什么时候等。我认为GCC早期的typeid有问题,但它是一个模糊的记忆。

【讨论】:

【参考方案4】:

我能看到的唯一效果是1 ? X : X 给你X 作为一个右值 而不是普通的X 这将是一个左值。这对于typeid() 来说对于数组(衰减为指针)之类的东西可能很重要,但我认为Derived 是否已知是一个类并不重要。也许它是从右值性确实重要的地方复制而来的?这将支持关于“货物崇拜编程”的评论

关于下面的评论我做了一个测试,果然typeid(array) == typeid(1 ? array : array),所以从某种意义上说我错了,但我的误解仍然可以匹配导致原始代码的误解!

【讨论】:

§5.16/4: "如果第二个和第三个操作数是左值并且具有相同的类型,则结果是该类型并且是一个左值。" 我认为 Visual C++ 搞错了(去挖掘 Connect 问题报告)。啊,这是使用条件运算符进行(不正确)右值转换的示例:connect.microsoft.com/VisualStudio/feedback/details/279444/…

以上是关于在 `typeid` 代码中奇怪地使用`?:`的主要内容,如果未能解决你的问题,请参考以下文章

C++ 静态多态性 (CRTP) 和使用派生类的 typedef

Firefox 中奇怪的表格渲染

由于 typeid().raw_name(),c++ 代码无法使用 GCC 编译 - 我该如何解决这个问题?

Swift 中奇怪的 UInt64 行为

Oracle中奇怪的SQL执行结果[关闭]

C++ 中奇怪的函数声明和 lambdas