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

时间:2011-07-22 20:43:09

标签: c++ conditional-operator micro-optimization typeid

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

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;
};

4 个答案:

答案 0 :(得分:47)

我认为是优化!一个鲜为人知且很少(你可以说“从不”)使用typeid的特性是typeid的参数的空取消引用引发异常而不是通常的UB。

  

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

事实上。是。否。

int *p = 0;
*p; // UB
typeid (*p); // throws

是的,这很丑陋,即使是语言丑陋的C ++标准。

OTOH,这在typeid的参数 之内不起作用,因此添加任何杂乱将取消此“功能”:

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

对于记录:我没有在此消息中声明编译器在进行取消引用之前自动检查指针不为null必然是一件疯狂的事情。我只是说当取消引用是typeid而不是其他地方的直接参数时进行此检查是完全疯狂的。 (也许是在某些草案中插入的恶作剧,从未删除过。)

对于记录:我没有在前面的“For the record”中声称编译器插入指针不为空的自动检查以及在a时抛出异常(如在Java中)是有意义的取消引用null:通常,在null取消引用上抛出异常是荒谬的。这是编程错误,因此异常无济于事。要求断言失败。

答案 1 :(得分:5)

我能看到的唯一效果是1 ? X : XX提供了 rvalue 而不是普通X,这将是左值< / em>的。对于像数组这样的事情(衰减到指针),这对typeid()来说很重要,但我认为如果已知Derived是一个类,那就不重要了。也许它是从rvalue-ness 重要的地方复制的?这将支持关于“货物崇拜节目”的评论

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

答案 2 :(得分:3)

[expr.typeid] / 2(N3936):

涵盖了此行为
  

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

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

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

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


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

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


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

据推测,程序员要非常高兴,永远不会使用空指针调用它,或者很高兴依赖于特定实现对空指针解除引用的处理。

答案 3 :(得分:0)

我怀疑有些编译器是

的简单情况
typeid(*m_basePtr)
无论运行时类型如何,

返回typeid(Base)始终。但是将它转换为表达式/临时/右值使编译器给出了RTTI。

问题是哪个编译器,何时等等。我认为GCC早期存在类型ID问题,但它是一个模糊的记忆。