带有typeid的逗号运算符

时间:2014-06-06 08:19:20

标签: c++ comma dynamic-cast typeid

我正在学习 A Generic Non-intrusive Smart Pointer Implementation 。我在第4节中有些困惑。一个陈述是

  

仅当结果类型是多态类类型的左值时,才会计算作为typeid运算符的参数提供的表达式。

相关的示例代码是:

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,如果结果类型是多态类类型的左值,则会评估q=dynamic_cast<void*>(p)结果表示评估dynamic_cast<void*>(p)(我猜)的结果,因此在任何情况下都必须应用dynamic_cast。文章陈述(据我所知),如果p不是多态那么dynamic_cast将不会应用,但为什么?在应用它之前,如何知道结果是否是多态的?如果有人详细描述了如何执行完整的陈述,将会很有帮助。

另一个陈述是

  

如果p为NULL,也会出现问题 - typeid将抛出std :: bad cast。

我看到的问题是,如果pNULL而不是typeid,则取消引用(虽然它可能会抛出bad_typeid,但这不是因为强制转换)。如果dynamic_castNULL,则void*会返回p类型为NULL的指针,而typeid应该能够推断出类型信息。这是一个错字,还是我错过了什么?

4 个答案:

答案 0 :(得分:11)

基本上编写以下代码

是一个奇特的技巧
if (T is polymorphic)
  return dynamic_cast<void*>(p);
else
  return static_cast<void*>(p);

使用的技巧是typeid(expr)以两种方式之一进行评估。如果编译器确定expr具有非多态类型,则继续使用其静态类型。但是如果expr具有动态类型,则它会在运行时评估expr。因此,当且仅当逗号后面的*p是多态的时,才会对逗号运算符之前的赋值进行求值。

出于这个原因,null的情况很复杂。如果T不是多态的,那么typeid(*p)在编译时被编译器替换,并且运行时空指针根本不重要。如果T是多态的,则应用空指针解引用的特殊处理,并且该特殊处理表明抛出了std::bad_typeid异常。

答案 1 :(得分:5)

逗号运算符具有从左到右的关联性,并从左到右进行求值。这意味着表达式q=dynamic_cast<void*>(p),*p的结果为*p。因此,仅当*p的类型是多态的时才评估动态强制转换。

关于NULL问题,标准规定:

  

当typeid应用于类型为a的左值表达式时   多态类型(10.3),结果是指一个type_info对象   表示最派生对象的类型(1.8)(即,   左值引用的动态类型。 如果左值表达式是   通过将一元*运算符应用于指针和指针而获得   是一个空指针值(4.10),typeid表达式抛出   bad_typeid例外(18.5.3)。

答案 2 :(得分:5)

解释

由于 C ++ 是一种statically typed语言,每个表达式的类型在编译时都是已知的,即使在涉及多态行为的情况下也是如此。

编译时是否知道某个类是否是多态的,在程序执行之间将会且不能更改此属性。

如果编译器发现typeid(expr)中的 expr 不会产生多态类类型的值,那么它只会使 expr “无法评估“,相当于在运行时不执行它。


<强>! ! !

重要的是要注意 expr 必须仍然有效,我们不能使用typeid来潜在地忽略格式错误的表达式;如果表达式格式错误,仍必须发出编译器诊断。

仅仅因为 expr “未评估”并不意味着我们可以让它包含一个格式错误的子表达式。

struct A { }; // not polymorphic

...

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

由于ptr不是指向多态类的指针,因此我们无法在dynamic_cast<T> T = void*中使用它,如[expr.dynamic.cast]p6中所述。


取消引用潜在的 nullptr

这将使typeid抛出异常,因此它不会产生未定义的行为,但如果使用此类实现,则应准备好处理异常。

  

5.2.8p2 类型识别 [expr.typeid]

     
    

(...)如果通过将一元*运算符应用于指针并且指针是空指针值(4.10)来获得glvalue表达式,则 typeid 表达式将抛出与类型为std::bad_typeid异常(18.8.3)的处理程序匹配的类型的异常(15.1)

  

澄清

  

这意味着如果结果类型是多态类类型的左值,则将评估q = dynamic_cast(p)。结果意味着评估dynamic_cast(p)的结果(我猜)。

不,作者所说的是,如果整个表达式的结果类型是多态类类型,则表达式将被评估。

注意“整个表达式”指的是...中的typeid(...),在您的情况下; q=dynamic_cast<void*>(p),*p


逗号操作员

逗号运算符将采用两个操作数 expr1 expr2 ,它将通过评估 expr1 开始,然后丢弃此值,之后它将评估 expr2 并产生该表达式的值。


将它们放在一起

这意味着使用逗号运算符的结果类型就好像它只包含右侧表达式,并且在下一行中编译器将检查以便 expr2 是一个多态类的类型。

typeid (expr1, expr2)

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

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

  

注意:在C ++ 03中,结果类型必须是多态左值 ,在C ++ 11中已更改为多态类类型的 glvalues 。功能

答案 3 :(得分:3)

  

结果表示评估dynamic_cast(p)的结果(I   猜测),所以必须在任何情况下应用dynamic_cast。该   文章陈述(据我所知),如果p不是多态的那么   不会应用dynamic_cast

如果任何相关类型之间没有多态关系,则不会应用

dynamic_cast,但能够转换为void*除外。

现在对于整个构造,如果通过具有良好警告的编译器运行它,您将看到

  

错误:不能用dynamic_cast'p'('struct A *'类型)来输入'void *'(源类型不是多态的)

或当A是多态的时,你会看到

  

警告:未使用计算的值[-Wunused-value]

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

所以在我看来,除了在源类型不是多态的情况下可能阻止编译之外,整个行根本没有任何意义

关于此处使用的内置逗号运算符:它从左到右进行计算,始终无条件地进行,其结果是最后一个元素。也就是说,结果相当于

typeid(*p);

没有被使用,因此是一个相当无用的结构。它是否会编译dynamic_cast中的非多态源类型,然后它将不会在运行时进行求值,因为*p的类型在编译时是已知的。

ISO14882:2001(e)§5.18-1

  

用逗号分隔的一对表达式从左到右进行评估;   左表达式是废弃值表达式(第5条)。 83 每个   值计算和与左表达式相关的副作用   在每个值计算和副作用相关之前进行排序   用正确的表达方式。结果的类型和值是   右操作数的类型和值;结果具有相同的值   category作为右操作数,如果是右操作数则是一个位字段   是一个glvalue和一个位域。

关于nullptr问题

  

如果p为NULL,也会出现问题 - typeid将抛出std :: bad cast。

标准中有一个特殊情况可以解决这个未定义的行为:

  

如果通过将一元*运算符应用于a来获得glvalue表达式   指针 68 并且指针是空指针值(4.10),typeid表达式抛出std :: bad_typeid   例外(18.7.3)。