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

时间:2011-05-17 01:59:51

标签: c++

我从“更有效的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&GT;为什么我们在这里使用static_cast而不是明显的dynamic_cast?

Q2&GT;在这种情况下,它们是否相同?

谢谢

// updated //

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

例如,

class base {};
class subclass : public base {};

base *pSubClass = new subclass;

subclass *pSubClass1 = static_cast<subClass*> (pSubClass); 

//虽然我知道我们应该在这里使用dynamic_cast,但static_cast是否正确地完成了工作?

7 个答案:

答案 0 :(得分:7)

为了记录,这是这样做的惯用方法:

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的指针”的右值,其中B是类类型,可以转换为“指向的指针”的右值cv2 D“,其中D是从B派生的类,如果从”指向D的指针“转换为”指向{{的指针“的有效标准转换1}}“存在, cv2 cv1 具有相同的cv资格,或更高的cv资格,B不是虚拟基类B。空指针值将转换为目标类型的空指针值。如果“指向 cv1 D的指针”的rvalue指向实际上是B类型对象的子对象的B,则生成指针指向D类型的封闭对象。否则,演员表的结果是未定义的。

答案 1 :(得分:3)

您已经自己验证了这些类型,因此您无需使用dynamic_cast。 Dynamic_cast会自动为您检查类型。

答案 2 :(得分:1)

为什么他们选择以这种方式实现它,而不是我不能说的更传统的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是来自Frigate的{​​{1}},则不会使用“太空飞船&lt; - &gt;太空飞船”碰撞程序。这很可能不是预期的行为;您可能希望SpaceShip使用该代码,但您必须手动为该新类型添加显式检查。

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


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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

如果std::bad_cast失败,某些编译器会生成抛出dynamic_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&) {}

看起来非常糟糕。

答案 5 :(得分:0)

首先,我认为重要的是要注意迈尔斯将此代码作为第一个双重调度的草编解决方案,然后转向不依赖于RTTI的优秀解决方案。

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

的实现 这里使用了

static_cast,因为我们已经确定对象属于目标类型,因此不需要再次为运行时检查付费。

那么为什么不首先使用dynamic_cast

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

答案 6 :(得分:0)

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

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

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

Nicht战争?

PS:试过这个并且它不起作用。嗯。有谁建议为什么?