为什么在 C++ 中向下转换然后分配给 base-class?
Why downcast and then assign to base-class in C++?
我偶然发现了以下代码结构,我想知道这是有意为之还是对转换机制的理解不足:
struct AbstractBase{
virtual void doThis(){
//Basic implementation here.
};
virtual void doThat()=0;
};
struct DerivedA: public AbstractBase{
virtual void doThis(){
//Other implementation here.
};
virtual void doThat(){
// some stuff here.
};
};
// More derived classes with similar structure....
// Dubious stuff happening here:
void strangeStuff(AbstractBase* pAbstract, int switcher){
AbstractBase* a = NULL;
switch(switcher){
case TYPE_DERIVED_A:
// why would someone use the abstract base pointer here???
a = dynamic_cast<DerivedA*>(pAbstract);
a->doThis();
a->doThat();
break;
// similar case statement with other derived classes...
}
}
// "main"
DerivedA* pDerivedA = new DerivedA;
strangeStuff( pDerivedA, TYPE_DERIVED_A );
我的猜测是,这个 dynamic_cast 语句只是缺乏理解和总体上非常糟糕的编程风格的结果(代码的整个工作方式,让我感到痛苦)并且它不会'不会导致此特定用例的行为发生任何变化。
但是,由于我不是演员方面的专家,我想知道是否有任何我不知道的细微副作用。
Blockquote [C++11: 5.2.7/9]: The value of a failed cast to pointer type is the null pointer value of the required result type.
如果类型错误,dynamic_cast
可以 return NULL
,导致以下行崩溃。因此,这可以是 1. 使(逻辑)错误更明确的尝试,或者 2. 某种代码内文档。
所以虽然它看起来不像是最好的设计,但强制转换没有任何效果也不完全正确。
我的猜测是编码员搞砸了。
第二个猜测是您在简化中跳过了 a
是否为 null 的检查。
第三种也是极不可能的可能性是,编码人员正在利用未定义的行为进行优化。
使用此代码:
a = dynamic_cast<DerivedA*>(pAbstract);
a->doThis();
如果 a
不是 DerivedA*
类型(或更多派生类型),则 a->doThis()
是未定义的行为。如果它是 DerivedA*
类型,那么 dynamic_cast
绝对什么都不做(保证)。
理论上,即使您没有更改 a
的类型,编译器也可以优化掉任何其他可能性,并保持一致的行为。即使稍后有人检查 a
是否为 null,在下一行执行未定义的行为也意味着编译器可以自由地不在 dynamic_cast
行上将 a
设置为 null。
我怀疑给定的编译器是否会这样做,但我可能是错的。
有些编译器会检测到某些路径会导致未定义的行为(在未来),从而消除这种可能性 在执行中向后 发生未定义行为的点在运动中,然后 "know" 有问题的代码不能处于会触发未定义行为的状态。然后它可以使用这些知识来优化有问题的代码。
这是一个例子:
std::string foo( unsigned int x ) {
std::string r;
if (x == (unsigned)-1)) {
r = "hello ";
}
int y = x;
std::stringstream ss;
ss << y;
r += ss.str();
return r;
}
编译器可以看到上面的y=x
行。如果 x
会使 int 溢出,则转换 y=x
是未定义的行为。无论第一个分支的结果如何,它都会发生。
简而言之,如果第一个分支运行,将导致未定义的行为。未定义的行为可以做任何事情,包括时间旅行——它可以回到过去并防止那个分支被采用。
所以
if (x == (unsigned)-1)) {
r = "hello ";
}
分支可以被优化器消除,在 C++ 中是合法的。
虽然上面只是一个玩具箱,但 gcc 做的优化非常像这样。有一个标志告诉它不要这样做。
(unsigned
-1
是已定义的行为,但在 C++ 中溢出 int
不是。实际上,这是因为有些平台会导致 signed int 溢出导致问题,并且 C++ 不想对它们强加额外成本来实现一致的实现。)
dynamic_cast
将确认动态类型确实与 switcher
变量指示的类型相匹配,从而使代码的危险性降低一些。但是,在不匹配的情况下,它会给出一个空指针,而代码忽略了检查。
但作者似乎并没有真正理解虚函数(用于统一处理多态类型)和RTTI(用于需要区分类型的罕见情况)的使用,并试图发明他们自己的手动、容易出错的类型识别形式。
我偶然发现了以下代码结构,我想知道这是有意为之还是对转换机制的理解不足:
struct AbstractBase{
virtual void doThis(){
//Basic implementation here.
};
virtual void doThat()=0;
};
struct DerivedA: public AbstractBase{
virtual void doThis(){
//Other implementation here.
};
virtual void doThat(){
// some stuff here.
};
};
// More derived classes with similar structure....
// Dubious stuff happening here:
void strangeStuff(AbstractBase* pAbstract, int switcher){
AbstractBase* a = NULL;
switch(switcher){
case TYPE_DERIVED_A:
// why would someone use the abstract base pointer here???
a = dynamic_cast<DerivedA*>(pAbstract);
a->doThis();
a->doThat();
break;
// similar case statement with other derived classes...
}
}
// "main"
DerivedA* pDerivedA = new DerivedA;
strangeStuff( pDerivedA, TYPE_DERIVED_A );
我的猜测是,这个 dynamic_cast 语句只是缺乏理解和总体上非常糟糕的编程风格的结果(代码的整个工作方式,让我感到痛苦)并且它不会'不会导致此特定用例的行为发生任何变化。
但是,由于我不是演员方面的专家,我想知道是否有任何我不知道的细微副作用。
Blockquote [C++11: 5.2.7/9]: The value of a failed cast to pointer type is the null pointer value of the required result type.
如果类型错误,dynamic_cast
可以 return NULL
,导致以下行崩溃。因此,这可以是 1. 使(逻辑)错误更明确的尝试,或者 2. 某种代码内文档。
所以虽然它看起来不像是最好的设计,但强制转换没有任何效果也不完全正确。
我的猜测是编码员搞砸了。
第二个猜测是您在简化中跳过了 a
是否为 null 的检查。
第三种也是极不可能的可能性是,编码人员正在利用未定义的行为进行优化。
使用此代码:
a = dynamic_cast<DerivedA*>(pAbstract);
a->doThis();
如果 a
不是 DerivedA*
类型(或更多派生类型),则 a->doThis()
是未定义的行为。如果它是 DerivedA*
类型,那么 dynamic_cast
绝对什么都不做(保证)。
理论上,即使您没有更改 a
的类型,编译器也可以优化掉任何其他可能性,并保持一致的行为。即使稍后有人检查 a
是否为 null,在下一行执行未定义的行为也意味着编译器可以自由地不在 dynamic_cast
行上将 a
设置为 null。
我怀疑给定的编译器是否会这样做,但我可能是错的。
有些编译器会检测到某些路径会导致未定义的行为(在未来),从而消除这种可能性 在执行中向后 发生未定义行为的点在运动中,然后 "know" 有问题的代码不能处于会触发未定义行为的状态。然后它可以使用这些知识来优化有问题的代码。
这是一个例子:
std::string foo( unsigned int x ) {
std::string r;
if (x == (unsigned)-1)) {
r = "hello ";
}
int y = x;
std::stringstream ss;
ss << y;
r += ss.str();
return r;
}
编译器可以看到上面的y=x
行。如果 x
会使 int 溢出,则转换 y=x
是未定义的行为。无论第一个分支的结果如何,它都会发生。
简而言之,如果第一个分支运行,将导致未定义的行为。未定义的行为可以做任何事情,包括时间旅行——它可以回到过去并防止那个分支被采用。
所以
if (x == (unsigned)-1)) {
r = "hello ";
}
分支可以被优化器消除,在 C++ 中是合法的。
虽然上面只是一个玩具箱,但 gcc 做的优化非常像这样。有一个标志告诉它不要这样做。
(unsigned
-1
是已定义的行为,但在 C++ 中溢出 int
不是。实际上,这是因为有些平台会导致 signed int 溢出导致问题,并且 C++ 不想对它们强加额外成本来实现一致的实现。)
dynamic_cast
将确认动态类型确实与 switcher
变量指示的类型相匹配,从而使代码的危险性降低一些。但是,在不匹配的情况下,它会给出一个空指针,而代码忽略了检查。
但作者似乎并没有真正理解虚函数(用于统一处理多态类型)和RTTI(用于需要区分类型的罕见情况)的使用,并试图发明他们自己的手动、容易出错的类型识别形式。