为什么在这里使用static_cast而不是dynamic_cast
Why use static_cast rather than dynamic_cast in here?
我从《更有效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
的虚拟基类。空指针值将转换为目标类型的空指针值。如果类型的 "指向 cv1B
的指针"的 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
是派生自SpaceShip
的Frigate
,它不会使用"飞船<->飞船"的碰撞套路。 这很有可能不是预期的行为;您可能希望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:试过这个,它不起作用。 嗯。 有人建议为什么吗?
- 如何理解C++标准N3337中的expr.const.cast子句8
- C++Cast运算符过载
- 在成员dynamic_bitset上使用 boost::from_block_range 时出错,但在本地dynamic
- C++类中的二维"dynamic"数组?
- 错误:"cast"未命名类型void setCastDescription(std::string
- 通过使用 const-cast 的非常量引用来延长临时的寿命
- "(void) cast"与功能有什么区别 "__attributes__"来沉默未使用的参数警告?
- protobuf in C++ with dynamic binding for google::protobuf::M
- 警告的原因是什么:"when type is in parentheses, array cannot have dynamic size"?
- C++:"Expected '(' for function-style cast or type construction"错误
- 为什么选择 g++ 给予者:"error: cast to pointer from integer of different size [-Werror=int-to-pointer-cast]"
- CUDA 错误:"dynamic initialization is not supported for __device__, __constant__ and __shared__ variabl
- Gtk+ g_signal_connect() 和 C++ lambda 会导致"invalid cast"错误
- 如何修复'The procedure entry point SDL_RWclose could not be located in the dynamic link library'
- Shared_ptr cast vs static_cast speed
- 在 iOS 上使用 Aruco 构建 OpenCV 时"Functional-style cast from id to double is not allowed"
- "The ordinal 344 could not be located in the dynamic link library"
- 覆盖 CAST 运算符(我认为它被称为向下转换)
- Dynamic Cast C++ Fail
- dynamic-cast-c++dynamic_cast错误处理