简体   繁体   English

带 typeid 的逗号运算符

[英]Comma operator with typeid

I was studying A Generic Non-intrusive Smart Pointer Implementation .我正在研究A Generic Non-intrusive Smart Pointer Implementation I have some confusion in section 4 .我在第 4 节有些困惑。 One statement is一种说法是

the expression supplied as the argument to the typeid operator is only evaluated if the result type is an lvalue of polymorphic class type.仅当结果类型是多态类类型的左值时,才会评估作为参数提供给 typeid 运算符的表达式。

And associated example code is:相关的示例代码是:

template<typename T>
  void* startOfObject(T* p) {
  void* q=static_cast<void*>(p);
  typeid(q=dynamic_cast<void*>(p),*p); // This line
  return q;
}

AFAIU, it means q=dynamic_cast<void*>(p) will be evaluated if the result type is an lvalue of polymorphic class type . AFAIU,这意味着如果结果类型多态类类型的左值,则将评估q=dynamic_cast<void*>(p) The result means the result of evaluating dynamic_cast<void*>(p) (I guess), so the dynamic_cast has to be applied in any case.结果意味着评估dynamic_cast<void*>(p)的结果(我猜),所以在任何情况下都必须应用dynamic_cast The articles states (as I understand) that if p is not polymorphic then dynamic_cast will not be applied, but why?文章指出(据我所知)如果p不是多态的,那么dynamic_cast将不会被应用,但为什么呢? Before applying it, how can it be known whether the result is polymorphic or not?在应用它之前,如何知道结果是否是多态的? It will be helpful if someone describes in details how the full statement will be executed.如果有人详细描述如何执行完整的语句,将会很有帮助。

Another statement is另一种说法是

There is also a problem if p is NULL – the typeid will throw a std::bad cast.如果 p 为 NULL 还有一个问题——typeid 会抛出一个 std::bad cast。

The problem I see is with de-referencing if p is NULL , not with typeid (although it may throw bad_typeid, but that is not because of casting).我看到的问题是如果pNULL则取消引用,而不是typeid (尽管它可能会抛出 bad_typeid,但这不是因为强制转换)。 dynamic_cast will return a NULL pointer of type void* if p is NULL , and typeid should be able to deduce the type information.如果pNULLdynamic_cast将返回void*类型的NULL指针,并且typeid应该能够推断出类型信息。 Is that a typo, or am I missing something?这是一个错字,还是我错过了什么?

It's a fancy trick to write essentially the following code基本上编写以下代码是一个花哨的技巧

if (T is polymorphic)
  return dynamic_cast<void*>(p);
else
  return static_cast<void*>(p);

The trick used is that typeid(expr) is evaluated in one of two ways.使用的技巧是typeid(expr)以两种方式之一进行评估。 If the compiler determines that expr has a non-polymorphic type, it goes ahead and uses its static type.如果编译器确定expr具有非多态类型,它将继续并使用其静态类型。 But if expr has a dynamic type, it evaluates expr at runtime.但是如果expr具有动态类型,它会在运行时计算expr The assignment before the comma operator is therefore evaluatad if and only if *p after the comma is polymorphic.因此,当且仅当逗号之后的*p是多态的时,逗号运算符之前的赋值才会被评估。

The null case is complex for that reason.出于这个原因,空案例很复杂。 If T is not polymorphic, then typeid(*p) is replaced by the compiler at compile time, and the runtime null pointer doesn't matter at all.如果 T 不是多态的,那么typeid(*p)在编译时被编译器替换,运行时空指针根本不重要。 If T is polymorphic, special handling of null pointer dereferences applies, and that special handling states that a std::bad_typeid exception is thrown.如果T是多态的,则应用空指针取消引用的特殊处理,并且该特殊处理声明抛出std::bad_typeid异常。

The comma operator has left-to-right associativity and is evaluated from left to right.逗号运算符具有从左到右的关联性,并且从左到右进行评估。 That means the result of the expression q=dynamic_cast<void*>(p),*p is *p .这意味着表达式q=dynamic_cast<void*>(p),*p的结果是*p So the dynamic cast is only evaluated if the type of *p is polymorphic.因此,仅当*p的类型是多态时才评估动态转换。

Regarding the NULL problem the standard states:关于NULL问题,标准规定:

When typeid is applied to an lvalue expression whose type is a polymorphic class type (10.3), the result refers to a type_info object representing the type of the most derived object (1.8) (that is, the dynamic type) to which the lvalue refers.当 typeid 应用于类型为多态类类型 (10.3) 的左值表达式时,结果引用一个 type_info 对象,该对象表示左值所指的最派生对象 (1.8) 的类型(即动态类型) . If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10), the typeid expression throws the bad_typeid exception (18.5.3).如果通过将一元 * 运算符应用于指针获得左值表达式,并且该指针是空指针值(4.10),则 typeid 表达式将抛出 bad_typeid 异常(18.5.3)。

Explanation解释

Since C++ is a statically typed language the type of every expression is known at compile-time, even in cases that involves polymorphic behavior.由于C++是一种静态类型语言,因此每个表达式的类型在编译时都是已知的,即使在涉及多态行为的情况下也是如此。

Whether a certain class is polymorphic or not is known at the time of compilation, this property will and cannot be changed between program executions.在编译时就知道某个类是否是多态的,这个属性将在程序执行之间也不能改变。

If the compiler sees that the expr in typeid(expr) will not yield a value of polymorphic class type, it will simply make expr "unevaluated" , which is equivalent of not executing it during run-time.如果编译器发现typeid(expr)中的expr不会产生多态类类型的值,它将简单地使expr "未评估" ,这相当于在运行时不执行它。


! ! !

It is important to note that the expr must still be valid, we cannot use typeid to potentially ignore a ill-formed expression;重要的是要注意expr必须仍然有效,我们不能使用typeid来潜在地忽略格式错误的表达式; a compiler diagnostic must still be issued if the expression is ill-formed.如果表达式格式不正确,仍必须发出编译器诊断。

Just because the expr will be "unevaluated" does not mean that we can have it contain an ill-formed sub-expression.仅仅因为expr将被“未评估”并不意味着我们可以让它包含一个格式错误的子表达式。

struct A { }; // not polymorphic

...

A * ptr = ...;
typeid (dynamic_cast<void*> (ptr), *ptr); // ill-formed

Since ptr is not a pointer to a polymorphic class we cannot use it in dynamic_cast<T> where T = void* , as specified in [expr.dynamic.cast]p6 .由于ptr不是指向多态类的指针,因此我们不能在[expr.dynamic.cast]p6中指定的T = void*dynamic_cast<T>中使用它。


Dereferencing a potential nullptr ?取消引用潜在的nullptr

This will make typeid throw an exception, so it will not yield undefined behavior but one should be prepared to handle an exception if one uses such implementation.这将使typeid抛出异常,因此它不会产生未定义的行为,但是如果使用这种实现,则应该准备好处理异常。

5.2.8p2 Type identification [expr.typeid] 5.2.8p2类型识别[expr.typeid]

(...) If the glvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10), the typeid expression throws an exception (15.1) of a type that would match a handler of type std::bad_typeid exception (18.8.3) (...) 如果通过将一元*运算符应用于指针来获得 glvalue 表达式,并且指针是空指针值 (4.10),则typeid表达式将抛出与以下处理程序匹配的类型的异常 (15.1)键入std::bad_typeid异常 (18.8.3)


Clarification澄清

It means q=dynamic_cast(p) will be evaluated if the result type is an lvalue of polymorphic class type.这意味着如果结果类型是多态类类型的左值,将评估 q=dynamic_cast(p)。 The result means the result of evaluating dynamic_cast(p) (I guess).结果意味着评估 dynamic_cast(p) 的结果(我猜)。

No, what the author is saying is that if the resulting type of the entire expression is a polymorphic class type, than the expression will be evaluated.不,作者的意思是,如果整个表达式的结果类型是多态类类型,则表达式将被评估。

Note : The "entire expression" refers to the ... in typeid(...) , in your case;注意:在您的情况下, “整个表达式”是指typeid(...)中的... q=dynamic_cast<void*>(p),*p . q=dynamic_cast<void*>(p),*p


The Comma Operator逗号运算符

The comma operator will take two operands expr1 and expr2 , it will start off by evaluating expr1 and then discard this value, after which it will evaluate expr2 and yield the value of this expression.逗号运算符将采用两个操作数expr1expr2 ,它将首先计算expr1然后丢弃这个值,之后它将计算expr2并产生这个表达式的值。


Putting it together把它放在一起

This means that the resulting type of using a comma operator is as if it only consisted of the right-hand-side expression, and in the following line the compiler will check so that the result of expr2 is a type which is a polymorphic class .这意味着使用逗号运算符的结果类型就好像它仅由右侧表达式组成,并且在以下行中编译器将检查expr2的结果是一个多态类的类型。

typeid (expr1, expr2)

typeid (q=dynamic_cast<void*>(p), *p)

// expr1 = q=dynamic_cast<void*>(p)
// expr2 = *p

Note : In C++03 the resulting type had to be a polymorphic lvalue , in C++11 this has been changed to glvalues of polymorphic class type.注意:在 C++03 中,结果类型必须是多态左值,在 C++11 中,这已更改为态类类型的左值。

The result means the result of evaluating dynamic_cast(p) (I guess), so the dynamic_cast has to be applied in any case.结果意味着评估 dynamic_cast(p) 的结果(我猜),所以在任何情况下都必须应用 dynamic_cast。 The articles states (as I understand) that if p is not polymorphic then dynamic_cast will not be applied文章指出(据我了解)如果 p 不是多态的,则不会应用 dynamic_cast

dynamic_cast will not be applied if any of the involved types have no polymorphic relationship with each other, with the exception of being able to cast to void* .如果任何涉及的类型彼此之间没有多态关系,则不会应用dynamic_cast ,但能够强制转换为void*除外。

Now for the whole construct, if you run it through a compiler that has good warnings, you will either see现在对于整个构造,如果您通过具有良好警告的编译器运行它,您将看到

error: cannot dynamic_cast 'p' (of type 'struct A*') to type 'void*' (source type is not polymorphic)错误:不能 dynamic_cast 'p'('struct A*' 类型)到类型 'void*'(源类型不是多态的)

or when A is polymorphic, you will see或者当 A 是多态的,你会看到

warning: value computed is not used [-Wunused-value]警告:未使用计算的值 [-Wunused-value]

for为了

typeid(q=dynamic_cast<void*>(p),*p); // this line

So in my opinion, the whole line makes no sense at all , other than to possibly prevent compilation when the source type is not polymorphic.所以在我看来,整行完全没有意义,除了在源类型不是多态时可能阻止编译。

As to the built in comma operator that is being used here: It is being evaluated from left to right, always unconditionally, and its result is the last element.至于这里使用的内置逗号运算符:它是从左到右评估的,总是无条件的,它的结果是最后一个元素。 That is, the result is equivalent to也就是说,结果等价于

typeid(*p);

which is not being used and thus a pretty useless construct.它没有被使用,因此是一个非常无用的结构。 Would it compile for non polymorphic source types in the dynamic_cast , then it would not be evaluated at runtime at all since the type of *p is known at compile time.它是否会针对dynamic_cast中的非多态源类型进行编译,然后在运行时根本不会对其进行评估,因为*p的类型在编译时是已知的。

ISO14882:2001(e) §5.18-1 ISO14882:2001(e) §5.18-1

A pair of expressions separated by a comma is evaluated left-to-right;一对用逗号分隔的表达式从左到右计算; the left expression is a discarded-value expression (Clause 5).左边的表达式是丢弃值表达式(第 5 条)。 83 Every value computation and side effect associated with the left expression is sequenced before every value computation and side effect associated with the right expression. 83与左表达式关联的每个值计算和副作用在与右表达式关联的每个值计算和副作用之前排序。 The type and value of the result are the type and value of the right operand;结果的类型和值是右操作数的类型和值; the result is of the same value category as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field.结果与其右操作数属于相同的值类别,并且如果其右操作数是泛左值和位域,则结果是位域。

Regarding the nullptr issue关于nullptr问题

There is also a problem if p is NULL – the typeid will throw a std::bad cast.如果 p 为 NULL 还有一个问题——typeid 会抛出一个 std::bad cast。

There is a special case in the standard to make this not undefined behaviour:标准中有一个特殊情况可以使这种行为不是未定义的:

If the glvalue expression is obtained by applying the unary * operator to a pointer 68 and the pointer is a null pointer value (4.10), the typeid expression throws the std::bad_typeid exception (18.7.3).如果通过将一元 * 运算符应用于指针68获得 glvalue 表达式,并且该指针是空指针值 (4.10),则 typeid 表达式将引发 std::bad_typeid 异常 (18.7.3)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM