我对dynamic_cast
感到非常困惑。 C ++ Primer 和cppreference(规则5)中的内容无法帮助我理解。 (推荐比书难得多,我都非常仔细地阅读了它们)
来自 C ++ Primer 5th :
dynamic_cast<type*>(e)
在所有情况下,
e
的类型必须是从目标类型公开派生的类类型,目标类型的public
基类或与目标类型相同。如果e
具有以下类型之一,则强制转换将成功...
这就是我对上面引用的文字的理解方式:
(基类具有虚函数)
dynamic_cast
成功:
e
是从type
继承的公共继承类。 e
是孩子。 cast。e
是type
的基类吗? type
是孩子。垂头丧气。e
与type
相同。边播吗?示例代码:
#include <iostream>
using namespace std;
struct A {
virtual void foo() {}
};
struct B : A {
};
struct C : B {
};
int main()
{
A* pa = new B;
if (C* pc = dynamic_cast<C*>(pa)) {
cout << "1"; //B is a base class of C
}
return 0;
}
我不明白为什么这次失败会失败,我认为它满足条件2 和规则5)(来自cppreference)。
如果这本书是错误的(再次该死),有人会从cppreference中详细说明rule 5)吗?没有示例我无法完全理解它的意思...
答案 0 :(得分:3)
最后一部分是对象的动态类型必须匹配。
在这里,您有一个B
指针指向的A
。您正在尝试动态地转换指针以获取指向C
的指针,但是没有指向的C
。因此强制转换失败。
动态强制转换不会创建对象,而只是允许您访问已经存在的对象。调用new B
时,它将创建一个B
子对象的A
对象。它不会创建C
对象。
答案 1 :(得分:3)
这是cppreference中带有我的注释的规则:
5)如果expression是多态类型Base的指针或引用, 并且new_type是对运行时派生的类型的指针或引用 检查已执行:
这适用。 B
是C
的基础。
a)指向/标识的最派生对象 检查表达。如果在该对象中表达式指向/引用 到Derived的公共基础,并且如果只有一个Derived类型的子对象 从由表达式指向/标识的子对象派生,然后 强制转换的结果/指向该派生子对象。 (这个 被称为“下注”。)
pa
指向的最引人注意的对象是B
类型。
尽管B
是C
的公共基础,但是pa
指向的特定实例不是{{1}的B
基础子对象的实例}实例。指向C
的实例是一个“具体”对象。因此,这种情况不适用。
一个例子:
B
b)否则,如果表达式指向/引用 到最衍生对象的公共基础,同时 大多数派生对象具有类型明确的公共基类 派生,转换点的结果/指的是派生(这是 被称为“边播”。)
C c;
B* bp = &c; // bp points to base subobject of C
C* cp = dynamic_cast<C*>(bp);
assert(cp);
B b2;
B* bp2 = &b2; // bp does not point to a base subobject
C* cp2 = dynamic_cast<C*>(bp2);
assert(!cp2);
并不指向基类为pa
的大多数派生对象,因此这种情况不适用。
旁投的示例:
C
c)否则,运行时检查将失败。如果 dynamic_cast用于指针,类型的空指针值 返回new_type 。如果将其用于引用,则异常 std :: bad_cast被抛出。
5a和5b都不适用,因此“ c”则为5c。
- e与类型相同。边播吗?
不是旁白。边播将在5b中说明。强制转换为相同类型只是一种身份强制转换(非常有用,因此也不是常用术语)。
这本书尝试的条件可能描述了转换是否格式正确。尽管“那么演员将成功” 当然似乎意味着更多。引用的规则不是正确的,用于描述强制转换在运行时是否成功。
如果整个程序格式正确,则编译器必须编译该程序。如果表达式格式错误,则编译器必须给您一条诊断消息,指出您做错了。
您显示的示例程序格式正确,必须成功编译。它确实可以在我的系统上编译。
答案 2 :(得分:1)
问题在于语句A* pa = new B;
创建了一个B
。
但是B
不包含C
(向下,向上或侧向),因此从pa
到C*
的动态转换肯定会失败。
答案 3 :(得分:0)
在大多数情况下,我会在以前的答案中添加另一种偏方的例子……
struct A {};
struct B { virtual ~B() = default; };
struct C : A, B {};
A *side_cast()
{
B *obj = new C;
return dynamic_cast<A *>(obj);
}
以上是合法的“强制转换”,并且不返回null。这表明目标类型都不:
*
expression 的静态类型相关。将其强制转换为在运行时成功。
强制转换是格式正确的,而不考虑 expression 所指向的最衍生对象的类型(IOW,{{1}的动态类型} 表达式)继承自目标类型。但是,除非*
是动态类型为static_cast<A *>(nullptr)
expression 的公共且明确的基类,否则它将返回A
。要点是,您可以合法地编写一大堆荒谬的类型转换,例如*
–请注意,dynamic_cast<std::tuple<int, float> *>(&std::cin)
的类型为std::cin
,它是多态的–但是您会得到一个在运行时为空指针。
简单地说,指针之间的std::istream
可以完成dynamic_cast
可以做的大部分事情(至少,非多态下降*除外),并且当静态类型为{{ 1}} 表达式是多态的,它也可以转换为指向类的任何指针,除了删除 cv-qualifers (static_cast
或*
)。但是,强制转换是否实际返回非null取决于上面提到的在运行时检查的特定条件。
*禁止这样做的原因是,没有安全的方法可以这样做,并且const
应该是安全的。因此,它们使您写volatile
来明确表明随后出现的UB是您的错。
答案 4 :(得分:0)
我想在@eerorika 的精彩回答之上加上我的两分钱,主要集中在
<块引用>如果只有一个派生类型的对象是从表达式指向/标识的子对象派生的,那么....
鉴于以下情况,我认为这是为了防止向下转型:
Base
|
Derived
/ \
| V
| Right
\ /
V V
MostDerived
在这种情况下,MostDerived 中有 2 个 Derived 对象。在将指向 MostDerived 实例的 Base 指针转换为 Derived 指针时,编译器不可能知道它指的是哪个 Derived 对象。 (MostDerived 引用的那个或 Right 引用的那个。)
事实上,这正是基址访问不明确的情况,会导致编译器警告或编译失败,具体取决于您的实现。