如何使用dynamic_cast正确下调?

时间:2018-09-28 13:57:13

标签: c++

我对dynamic_cast感到非常困惑。 C ++ Primer cppreference(规则5)中的内容无法帮助我理解。 (推荐比书难得多,我都非常仔细地阅读了它们)

来自 C ++ Primer 5th
dynamic_cast<type*>(e)

  

在所有情况下,e的类型必须是从目标类型公开派生的类类型,目标类型的public基类或与目标类型相同。如果e具有以下类型之一,则强制转换将成功...

这就是我对上面引用的文字的理解方式:

(基类具有虚函数)

dynamic_cast成功:

  1. e是从type继承的公共继承类。 e是孩子。 cast。
  2. etype的基类吗? type是孩子。垂头丧气。
  3. etype相同。边播吗?

示例代码:

#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)吗?没有示例我无法完全理解它的意思...

5 个答案:

答案 0 :(得分:3)

最后一部分是对象的动态类型必须匹配。

在这里,您有一个B指针指向的A。您正在尝试动态地转换指针以获取指向C的指针,但是没有指向的C。因此强制转换失败。

动态强制转换不会创建对象,而只是允许您访问已经存在的对象。调用new B时,它将创建一个B子对象的A对象。它不会创建C对象。

答案 1 :(得分:3)

这是cppreference中带有我的注释的规则:

  

5)如果expression是多态类型Base的指针或引用,   并且new_type是对运行时派生的类型的指针或引用   检查已执行:

这适用。 BC的基础。

  

a)指向/标识的最派生对象   检查表达。如果在该对象中表达式指向/引用   到Derived的公共基础,并且如果只有一个Derived类型的子对象   从由表达式指向/标识的子对象派生,然后   强制转换的结果/指向该派生子对象。 (这个   被称为“下注”。)

pa指向的最引人注意的对象是B类型。

尽管BC的公共基础,但是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。


  
      
  1. e与类型相同。边播吗?
  2.   

不是旁白。边播将在5b中说明。强制转换为相同类型只是一种身份强制转换(非常有用,因此也不是常用术语)。


这本书尝试的条件可能描述了转换是否格式正确。尽管“那么演员将成功” 当然似乎意味着更多。引用的规则不是正确的,用于描述强制转换在运行时是否成功。

如果整个程序格式正确,则编译器必须编译该程序。如果表达式格式错误,则编译器必须给您一条诊断消息,指出您做错了。

您显示的示例程序格式正确,必须成功编译。它确实可以在我的系统上编译。

答案 2 :(得分:1)

问题在于语句A* pa = new B;创建了一个B

但是B不包含C(向下,向上或侧向),因此从paC*的动态转换肯定会失败。

答案 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 引用的那个。)

事实上,这正是基址访问不明确的情况,会导致编译器警告或编译失败,具体取决于您的实现。