为什么在 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(用于需要区分类型的罕见情况)的使用,并试图发明他们自己的手动、容易出错的类型识别形式。