为什么在这里使用static_cast而不是dynamic_cast

Why use static_cast rather than dynamic_cast in here?

本文关键字:cast dynamic 在这里 static 为什么      更新时间:2023-10-16

我从《更有效C++》一书中复制了以下文字。

第 31 项:使函数相对于多个对象成为虚拟函数。

class GameObject { ... };
class SpaceShip: public GameObject { ... };
class SpaceStation: public GameObject { ... };
class Asteroid: public GameObject { ... };

最常见的双重调度方法通过 if-then-elses 链将我们返回到虚拟函数仿真的无情世界。在这个严酷的世界中,我们首先发现 otherObject 的真实类型,然后针对所有可能性对其进行测试:

void SpaceShip::collide(GameObject& otherObject)
{
  const type_info& objectType = typeid(otherObject);
  if (objectType == typeid(SpaceShip)) {
    SpaceShip& ss = static_cast<SpaceShip&>(otherObject);
    process a SpaceShip-SpaceShip collision;
  }
  else if (objectType == typeid(SpaceStation)) {
    SpaceStation& ss =
      static_cast<SpaceStation&>(otherObject);
    process a SpaceShip-SpaceStation collision;
  }
...
}

问题是:

Q1> 为什么我们在这里使用static_cast而不是明显的dynamic_cast?

Q2> 在这种情况下它们是一样的吗?

谢谢

//更新//

事实上,我对问题2更感兴趣。

例如

class base {};
class subclass : public base {};
base *pSubClass = new subclass;
subclass *pSubClass1 = static_cast<subClass*> (pSubClass); 

在这种情况下,static_cast是否正确完成工作,尽管我知道我们应该在这里使用dynamic_cast?

作为记录,以下是这样做的惯用方法:

void SpaceShip::collide(GameObject& otherObject)
{
    if (SpaceShip* ss = dynamic_cast<SpaceShip*>(&otherObject)) {
        // process a SpaceShip-SpaceShip collision;
    }
    else if (SpaceStation* ss = dynamic_cast<SpaceStation*>(&otherObject)) {
        // process a SpaceShip-SpaceStation collision;
    }
    // ...
}

它更短,表现出相同的性能特征,最重要的是,它是惯用C++,不会让其他程序员挠头并想知道有什么意义。


编辑

(响应OP的编辑):

是的,这是明确定义的行为。以下是 C++03 标准 §5.2.9/8 的内容:

类型为"指向 cv1 B 的指针"的 rvalue,其中 B 是类类型,可以转换为类型为 "指向 cv2 的指针D"的 rvalue,其中 D 是从 B 派生的类,如果存在从"指向D的指针"到"指向B的指针"的有效标准转换,则 cv2 与 cv 资格相同, 或比 CV1 更高的 CV 资格,并且 B 不是D的虚拟基类。空指针值将转换为目标类型的空指针值。如果类型的 "指向 cv1 B 的指针"的 rvalue 指向一个实际上是 D 类型的对象的子对象的B,则生成的指针指向类型 D 的封闭对象。否则,转换的结果是未定义的。

您已经自己验证了类型,因此不需要使用 dynamic_cast。 Dynamic_cast将自动为您检查类型。

为什么他们选择以这种方式实现它,而不是更传统的dynamic_cast我不能说,但这两个选项的行为不一定相同。 如前所述,该代码仅考虑参数的实际类型,而dynamic_cast考虑参数在继承树中的位置。 考虑:

struct Base { virtual ~Base() { } };
struct Intermediate : Base { };
struct Derived : Intermediate { };
int main() {
    Intermediate i;
    Base* p_i = &i;
    Derived d;
    Base* p_d = &d;
    assert(typeid(*p_i) == typeid(Intermediate)); //1
    assert(dynamic_cast<Intermediate*>(p_i)); //2
    assert(typeid(*p_d) == typeid(Intermediate)); //3
    assert(dynamic_cast<Intermediate*>(p_d)); //4
}

(1) 和 (2) 都通过了它们的断言,但 (3) 失败,而 (4) 成功。 p_d指向Derived对象,因此type_id生成Derived对象的信息,这些信息不会与Intermediate对象的信息进行比较。 但是Derived是从Intermediate派生出来的,所以dynamic_cast会很乐意将指针转换为Derived转换为指向Intermediate的指针。

用原题用的术语来说,如果otherObject是派生自SpaceShipFrigate,它不会使用"飞船<->飞船"的碰撞套路。 这很有可能不是预期的行为;您可能希望Frigate使用该代码,但您必须手动为该新类型添加显式检查。

当然,如果您只检查从未继承的类型,则这种差异就会消失。 或者,如果您只是不想要多态行为(尽管这会使标题具有误导性)。 在这种情况下,这可能会提高性能,但这是一个巨大的实现细节,我当然不会在实践中投入资金。


如果类型不是多态的,则会发生另一个很小且基本上无关紧要的差异。 在我上面的代码中,如果从Base中删除虚拟析构函数,(2) 和 (4) 现在表现出未定义的行为。 (1)和(3)仍然定义明确,但现在毫无价值;两者都会失败,因为typeid(*p_i)会产生有关Base的信息,而不是像以前那样Intermediate

似乎是一个非常可靠的答案。 基本上静态强制转换更快,但不执行运行时类型检查。

某些编译器将生成在dynamic_cast失败时抛出std::bad_cast的代码。因此,在这种情况下,这两种方法是不同的。使用dynamic_cast可能看起来像

try {
    SpaceShip& ship = dynamic_cast<SpaceShip&>(otherObject);
    // collision logic
    return;
} catch (std::bad_cast&) {}
try {
    SpaceStation& station = dynamic_cast<SpaceStation&>(otherObject);
    // collision logic
    return;
} catch (std::bad_cast&) {}

这看起来真的很糟糕。

首先,我认为重要的是要注意,在转向不依赖于RTTI的高级解决方案之前,Myers将此代码作为双重调度的第一个稻草人解决方案。

首先回答第二个问题,是的,这相当于使用dynamic_cast.

这里使用static_cast是因为我们已经确定该对象是目标类型,因此不需要再次为运行时检查付费。

那么为什么不首先使用dynamic_cast呢?

我怀疑迈尔斯是这样写的,因为这将是一连串无限数量的if ()支票。 他本可以做一些@ildjarn建议的事情,但这需要为他想要检查的每个类型声明一个新变量。 我的怀疑是他只是喜欢他提出的更好的美学。

也许我弄错了,但是...我的理解是,所有 RTTI 实现都涉及某种查找/搜索,以查找传递给 dynamic_cast 或 typeinfo 的对象类型。

除非量子效应,否则此搜索必须花费可测量的周期数才能完成,并且在OP的代码中,搜索结果被缓存,而在dynamic_cast示例中,搜索在每个条件中重复。

因此,缓存版本必须更快。 记住关于过早优化的警告,我认为它也很容易上眼。

尼赫特战争?

PS:试过这个,它不起作用。 嗯。 有人建议为什么吗?