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

我认为这是一个优化 ! 有一点已知,很less(可以说“从不”)使用typeid特性是typeid的参数的null解引用抛出exception,而不是通常的UB。

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

确实。 是。 没有。

 int * p = 0;
 * P;  // UB
 typeid(* p);  //抛出

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

OTOH,这 typeid的参数里面不起作用,所以添加任何混乱将取消这个“特性”:

 int * p = 0;
 typeid(1?* p:* p);  // UB
 typeid的(标识(* P));  // UB

为了logging:在这个消息中,我没有声称由编译器自动检查一个指针在进行解引用之前是不是空的,这一定是件疯狂的事情。 我只是说,这样做是为了检查何时解除引用是typeid的直接参数, 而不是其他地方 ,是完全疯狂的。 (也许是在一些草案插入一个恶作剧,并从未删除。)

为了logging:我没有在前面的“为了logging”中声称,编译器插入指针不为空的自动检查是有意义的,并且当空值被解除引用时抛出exception(如在Java中) :一般来说,抛出一个空引用exception是荒谬的。 这是一个编程错误,所以一个例外将无济于事。 要求断言失败。

我能看到的唯一效果是1 ? X : X 1 ? X : X给你X作为一个右值,而不是普通的X ,这将是一个左值 。 对于像数组这样的事情来说,这可能很重要(对指针进行衰减),但是我不认为Derived是否是一个类是不重要的。 也许它是从哪个地方复制出来的? 这将支持关于“货物崇拜节目”的评论

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

此行为由[expr.typeid] / 2(N3936)覆盖:

typeid应用于types为多态类types的glvalueexpression式时,结果引用std::type_info对象,该对象表示glvalue引用的最派生对象(即dynamictypes)的types。 如果glvalueexpression式是通过将unary *运算符应用于指针而获得的,并且该指针是一个空指针值,则typeidexpression式将抛出一个typesexception,该types将匹配std::bad_typeidexceptiontypes的处理程序。

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

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

因此,这段代码确实是“当typeid应用于types为多态类types的glvalueexpression式”的一个例子。


现在我们可以阅读以上报价的其余部分。 glvalueexpression式不是 “通过将一元操作符应用于指针而获得的” – 它是通过三元运算符获得的。 因此,如果m_basePtr为空,则标准不要求引发exception。

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


最后: 为什么有人会写这个? 我认为curiousguy的答案是迄今为止最可能的build议:通过这个构造,编译器不必插入空指针testing和代码来生成exception,所以它是一个微型优化。

大概程序员要么是非常高兴,以至于永远不会用空指针来调用,要么依赖于特定的实现对空指针解引用的处理。

我怀疑是一些编译器,为简单的情况

 typeid(*m_basePtr) 

无论运行时types如何, 总是返回typeid(Base)。 但是把它转换成expression式/临时/右值使得编译器给出了RTTI。

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