【问题标题】:Why use static_cast rather than dynamic_cast in here?为什么在这里使用 static_cast 而不是 dynamic_cast?
【发布时间】:2011-05-17 01:59:50
【问题描述】:

我从《更有效的 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); 

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

【问题讨论】:

  • 真正的问题是:为什么在这里使用dynamic_cast 而不是static_cast

标签: c++


【解决方案1】:

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

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++,不会让其他程序员摸不着头脑,想知道重点是什么。


EDIT(响应 OP 的编辑):

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

类型为“pointer to cv1 B”的右值,其中B是一个类类型,可以转换为类型为“pointer to cv2”的右值> D”,其中D 是派生自B 的类,如果存在从“指向D”的有效标准转换为“指向B”的指针,cv2cv1 的 cv 限定相同或更高,并且B 不是D 的虚拟基类。空指针值转换为目标类型的空指针值。如果“指向 cv1 B 的指针”类型的右值指向实际上是 D 类型对象的子对象的 B,则生成的指针指向封闭对象D 类型。否则,强制转换的结果是未定义的。

【讨论】:

  • “相同的性能”是一个相当强烈的主张。也许在 Big-Oh 的意义上,因为这两种方法都会导致一系列 if 测试。但是没有理由相信测试的实际评估会导致执行类似的代码,更不用说完全相同了。
  • @Dennis Zickefoose:你是对的——很可能,这段代码也比 OP 更有效。
  • @ildjarn:这取决于。在某些平台上,dynamic_cast 的实现是出了名的糟糕。在所有平台上,结果将取决于类型层次结构的复杂性。我同意这几乎肯定应该是这样编写代码的,但是dynamic_cast 增加的通用性确实是有代价的。
  • @Dennis Zickefoose:我在开玩笑。关键是,我不知道有任何 C++ 编译器具有不止一种 RTTI 实现。鉴于 RTTI 的单一实现,我无法想象比较 typeids 比 dynamic_cast 更贵或更便宜的情况。
【解决方案2】:

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

【讨论】:

  • 当然,没有充分的理由手动验证类型然后进行转换,而不仅仅是使用 dynamic_cast 开始。也就是说,这段代码没有明显的原因是非惯用的。
  • @ildjarn 代码手动检查类型,因为它根据otherObject 的类型执行不同的操作。
  • 这可以通过检查 dynamic_cast 的结果是否为空来以惯用方式(并且更简洁)来完成。看我的回答。
  • @ildjam 感谢您发布您的答案。我一开始没听懂你在说什么。我同意,你的方式似乎更好。 (正如所指出的,它接受原始类不接受的派生类,但这似乎是一个好处。)
【解决方案3】:

为什么他们选择以这种方式实现它,而不是更传统的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 是从SpaceShip 派生的Frigate,它将不会使用“spaceshipspaceship”碰撞例程。这很可能不是预期的行为;您可能希望Frigate 使用该代码,但您必须手动添加对该新类型的显式检查。

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


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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 但是手动进行运行时测试必须消除大部分优势。
    【解决方案5】:

    如果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&) {}
    

    看起来很糟糕。

    【讨论】:

    • bad_cast 仅在转换引用时抛出,因为引用不能是0,而指针可以。
    【解决方案6】:

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

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

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

    那么为什么不首先使用dynamic_cast

    我怀疑 Myers 是这样写的,因为这将是一个不定数量的if () 检查链。他本可以像@ildjarn 建议的那样做一些事情,但这将涉及为他想要检查的每种类型声明一个新变量。我怀疑他只是更喜欢他所提出的美学。

    【讨论】:

      【解决方案7】:

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

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

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

      一战?

      PS:试过了,还是不行。嗯。有人建议为什么?

      【讨论】:

        猜你喜欢
        • 2012-02-26
        • 1970-01-01
        • 1970-01-01
        • 2019-04-14
        • 2010-09-11
        • 2016-11-10
        • 1970-01-01
        • 2010-09-24
        相关资源
        最近更新 更多