我正在学习一个通用的非侵入式智能指针实现。我在第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,也会有一个问题——类型id会抛出一个std::错误的强制类型转换。
我看到的问题是取消引用,如果p
是NULL
,而不是typeid
(虽然它可能抛出bad_typeid,但这不是因为铸造)。如果p
是NULL
, dynamic_cast
将返回类型为void*
的NULL
指针,typeid
应该能够推断出类型信息。是打错了,还是我漏了什么?
写下面的代码是一个奇妙的技巧
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
异常。
逗号操作符具有从左到右的结合性,从左到右求值。这意味着表达式q=dynamic_cast<void*>(p),*p
的结果是*p
。因此,只有当*p
的类型是多态的时,才计算动态强制转换。
关于NULL
问题,标准规定:
当typeid应用于类型为a的左值表达式时多态类类型(10.3),结果引用type_info对象表示最派生对象(1.8)的类型(即(动态类型)左值所指向的对象。如果左值表达式为通过对指针和指针应用一元*操作符获得如果是空指针值(4.10),则typeid表达式抛出
说明
由于 c++ 是一种静态类型的语言,因此每个表达式的类型在编译时都是已知的,即使在涉及多态行为的情况下也是如此。
某个类在编译时是否为多态是已知的,此属性将不会在程序执行之间被更改。
如果编译器看到typeid(expr)
中的expr不会产生多态类类型的值,它将简单地使expr为"未求值",这相当于在运行时不执行它。
!! !
重要的是要注意expr必须仍然有效,我们不能使用typeid
来潜在地忽略错误格式的表达式;如果表达式格式错误,仍然必须发出编译器诊断。
仅仅因为expr将"未求值"并不意味着我们可以让它包含一个病态的子表达式。
struct A { }; // not polymorphic
...
A * ptr = ...;
typeid (dynamic_cast<void*> (ptr), *ptr); // ill-formed
由于ptr
不是一个指向多态类的指针,我们不能在[expr.dynamic.cast]p6
中指定T = void*
的dynamic_cast<T>
中使用它。
解引用一个潜在的nullptr?
这将使typeid
抛出一个异常,所以它不会产生未定义的行为,但是如果使用这样的实现,应该准备好处理异常。
5.2.8p2
类型识别[expr.typeid]
(…)如果glvalue表达式是通过对指针应用一元
*
运算符获得的,并且该指针是空指针值(4.10),则typeid表达式抛出一个异常(15.1),该异常的类型与std::bad_typeid
exception(18.8.3)处理程序的类型匹配。
澄清
表示如果结果类型是多态类类型的左值,将计算q=dynamic_cast(p)。结果是指dynamic_cast(p)的计算结果(I guess)。
不,作者所说的是,如果整个表达式的结果类型是一个多态类类型,那么表达式将被评估。
注意: "整个表达式"指的是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中,结果类型必须是一个多态的lvalue,在c++ 11中,这已被更改为多态类类型的glvalues。
结果是指dynamic_cast(p) (I)的计算结果猜测),因此在任何情况下都必须应用dynamic_cast。的文章指出(据我所知),如果p不是多态的,那么Dynamic_cast将不被应用
dynamic_cast
将不适用,如果任何涉及的类型之间没有多态关系,除了可以强制转换为void*
。
现在对于整个结构,如果您通过具有良好警告的编译器运行它,您将看到
错误:cannot dynamic_cast 'p'(类型'struct A*')类型'void*'(源类型不是多态的)
或者当A是多态的,你会看到
警告:value未被使用[- unused-value]
typeid(q=dynamic_cast<void*>(p),*p); // this line
所以在我看来,整行根本没有意义,除了可能在源类型不是多态时防止编译。
关于这里使用的内置逗号操作符:它总是无条件地从左到右求值,其结果是最后一个元素。也就是说,结果相当于
typeid(*p);
没有被使用,因此是一个非常无用的结构。如果它在dynamic_cast
中编译非多态源类型,那么它将不会在运行时被评估,因为*p
的类型在编译时是已知的。
ISO14882:2001 (e)§5.18 - 1
用逗号分隔的一对表达式从左到右求值;左边的表达式是一个丢弃值表达式(第5条)。83 Every与左表达式相关的值计算和副作用在每个值计算和副作用关联之前进行排序用正确的表达。结果的类型和值为右操作数的类型和值;结果是相同的值类别作为其右操作数,如果其为右操作数,则为位域是一个全局值和位域。
关于nullptr
问题
如果p是NULL,也会有一个问题——类型id会抛出一个std::错误的强制类型转换。
在标准中有一个特殊的情况使此行为不是未定义的:
如果对a应用一元*运算符获得全局值表达式指针68且指针为空指针值(4.10),typeid表达式抛出std::bad_typeid异常(18.7.3)。