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

发布于 2024-11-26 10:25:26 字数 674 浏览 0 评论 0原文

在我正在从事的一个项目中,我看到了这样的代码,

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

In one of the projects I'm working on, I'm seeing this code

struct Base {
  virtual ~Base() { }
};

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

I have never seen typeid used like that. Why does it do that weird dance with ?:, instead of just doing typeid(*m_basePtr)? Could there be any reason? Base is a polymorphic class (with a virtual destructor).

EDIT: At another place of this code, I'm seeing this and it appears to be equivalently "superfluous"

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

眼眸 2024-12-03 10:25:26

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

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

的确。是的。不。

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

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

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

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

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

郑重声明:我并不是在前面的“郑重声明”中声称编译器插入指针不为空的自动检查是有意义的,以及在取消引用 null 时抛出异常(如在 Java 中):一般来说,在取消引用 null 时引发异常是荒谬的。这是一个编程错误,因此异常没有帮助。需要断言失败。

I think it is an optimisation! A little known and rarely (you could say "never") used feature of typeid is that a null dereference of the argument of typeid throws an exception instead of the usual UB.

What? Are you serious? Are you drunk?

Indeed. Yes. No.

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

Yes, this is ugly, even by the C++ standard of language ugliness.

OTOH, this does not work anywhere inside the argument of typeid, so adding any clutter will cancel this "feature":

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

For the record: I am not claiming in this message that automatic checking by the compiler that a pointer is not null before doing a dereference is necessarily a crazy thing. I am only saying that doing this check when the dereference is the immediate argument of typeid, and not elsewhere, is totally crazy. (Maybe is was a prank inserted in some draft, and never removed.)

For the record: I am not claiming in the previous "For the record" that it makes sense for the compiler to insert automatic checks that a pointer is not null, and to to throw an exception (as in Java) when a null is dereferenced: in general, throwing an exception on a null dereference is absurd. This is a programming error so an exception will not help. An assertion failure is called for.

初心 2024-12-03 10:25:26

我能看到的唯一效果是 1 ? X : X 为您提供 X 作为右值,而不是普通的 X ,后者将是左值 。对于数组(衰减为指针)之类的东西,这对 typeid() 可能很重要,但我认为如果知道 Derived 是一个类,那也不重要。也许它是从右值性重要的某个地方复制的?这将支持关于“货物邪教编程”的评论

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

The only effect I can see is that 1 ? X : X gives you X as an rvalue instead of plain X which would be an lvalue. This can matter to typeid() for things like arrays (decaying to pointers) but I don't think it would matter if Derived is known to be a class. Perhaps it was copied from someplace where the rvalue-ness did matter? That would support the comment about "cargo cult programming"

Regarding the comment below I did a test and sure enough typeid(array) == typeid(1 ? array : array), so in a sense I'm wrong, but my misunderstanding could still match the misunderstanding that lead to the original code!

如此安好 2024-12-03 10:25:26

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

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

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

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

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


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

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


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

据推测,程序员要么很高兴这永远不会用空指针调用,要么很高兴依赖特定实现对空指针取消引用的处理。

This behaviour is covered by [expr.typeid]/2 (N3936):

When typeid is applied to a glvalue expression whose type is a polymorphic class type, the result refers to a std::type_info object representing the type of the most derived object (that is, the dynamic type) to which the glvalue refers. If the glvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value, the typeid expression throws an exception of a type that would match a handler of type std::bad_typeid exception.

The expression 1 ? *p : *p is always an lvalue. This is because *p is an lvalue, and [expr.cond]/4 says that if the second and third operand to the ternary operator have the same type and value category, then the result of the operator has that type and value category also.

Therefore, 1 ? *m_basePtr : *m_basePtr is an lvalue with type Base. Since Base has a virtual destructor, it is a polymorphic class type.

Therefore, this code is indeed an example of "When typeid is applied to a glvalue expression whose type is a polymorphic class type" .


Now we can read the rest of the above quote. The glvalue expression was not "obtained by applying the unary * operator to a pointer" - it was obtained via the ternary operator. Therefore the standard does not require that an exception be thrown if m_basePtr is null.

The behaviour in the case that m_basePtr is null would be covered by the more general rules about dereferencing a null pointer (which are a bit murky in C++ actually but for practical purposes we'll assume that it causes undefined behaviour here).


Finally: why would someone write this? I think curiousguy's answer is the most plausible suggestion so far: with this construct, the compiler does not have to insert a null pointer test and code to generate an exception, so it is a micro-optimization.

Presumably the programmer is either happy enough that this will never be called with a null pointer, or happy to rely on a particular implementation's handling of null pointer dereference.

暖树树初阳… 2024-12-03 10:25:26

我怀疑某些编译器对于总是返回 typeid(Base) 的简单情况

typeid(*m_basePtr)

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

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

I suspect some compiler was, for the simple case of

typeid(*m_basePtr)

returning typeid(Base) always, regardless of the runtime type. But turning it to an expression/temporary/rvalue made the compiler give the RTTI.

Question is which compiler, when, etc. I think GCC had problems with typeid early on, but it is a vague memory.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文