简体   繁体   English

如何使用 dynamic_cast 正确向下转换?

[英]How to use dynamic_cast to downcast correctly?

I am being very confused about dynamic_cast .我对dynamic_cast感到非常困惑。 Material from C++ Primer and cppreference (rule 5) can't help me understand.来自C++ Primercppreference (规则 5)的材料无法帮助我理解。 (cppreference is way much harder than the book and I read them both very carefully) (cppreference 比这本书难多了,我非常仔细地阅读了它们)

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

In all cases, the type of e must be either a class type that is publicly derived from the target type, a public base class of the target type, or the same as the target type.在所有情况下, e的类型必须是从目标类型公开派生的类类型、目标类型的public基类,或者与目标类型相同。 If e has one of these types, then the cast will succeed...如果e具有这些类型之一,则转换将成功...

So here's how I understand the quoted text above:所以这就是我如何理解上面引用的文字:

(Base class have virtual functions) (基类有虚函数)

dynamic_cast succeeds if : dynamic_cast成功,如果:

  1. e is a public inherited derived class from type . e是从type公共继承的派生类。 e is children. e是儿童。 Upcast.抬头。
  2. e is a base class of type ? etype的基类? type is children. type是儿童。 Downcast.沮丧。
  3. e is same as type . etype相同。 Sidecast?侧播?

Sample code:示例代码:

#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;
}

I don't understand why this downcast would fail, I think it satisfies condition 2. and rule 5) (from cppreference).我不明白为什么这种向下转型会失败,我认为它满足条件 2.规则 5) (来自 cppreference)。


If the book is wrong(damn once again), would someone elaborate rule 5) from cppreference?如果这本书是错误的(再次该死),有人会详细说明 cppreference 中的规则 5)吗? I can't fully understand what it say without examples...没有例子我无法完全理解它所说的......

Here is the rule from cppreference with my annotations:这是 cppreference 中带有我的注释的规则:

5) If expression is a pointer or reference to a polymorphic type Base, and new_type is a pointer or reference to the type Derived a run-time check is performed: 5) 如果 expression 是指向多态类型 Base 的指针或引用,并且 new_type 是指向类型 Derived 的指针或引用,则执行运行时检查:

This applies.这适用。 B is a base of C . BC的基数。

a) The most derived object pointed/identified by expression is examined. a) 检查表达式指向/标识的最派生对象。 If, in that object, expression points/refers to a public base of Derived, and if only one subobject of Derived type is derived from the subobject pointed/identified by expression, then the result of the cast points/refers to that Derived subobject.如果在该对象中,表达式指向/引用派生的公共基础,并且如果只有一个派生类型的子对象从表达式指向/标识的子对象派生,则转换的结果指向/引用该派生子对象。 (This is known as a "downcast".) (这被称为“垂头丧气”。)

The most dervied object pointed by pa is of type B . pa指向的最衍生的对象是B类型。

Although B is a public base of C , the particular instance which pa points to, is not an instance of B base subobject of a C instance.尽管BC的公共基础,但pa指向的特定实例不是C实例的B基础子对象的实例。 The pointed B instance is a "concrete" object.指向的B实例是一个“具体”对象。 So, this case does not apply.所以,本案不适用。

An example:一个例子:

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);

b) Otherwise, if expression points/refers to a public base of the most derived object, and, simultaneously, the most derived object has an unambiguous public base class of type Derived, the result of the cast points/refers to that Derived (This is known as a "sidecast".) b) 否则,如果表达式指向/引用最派生对象的公共基类,并且同时,最派生对象具有派生类型的明确公共基类,则转换的结果指向/引用那个派生对象(这被称为“侧播”。)

pa does not point to a most derived object whose base class is C , so this case does not apply. pa不指向基类为C的最派生对象,因此这种情况不适用。

An example of side cast:侧铸的一个例子:

struct base {
    virtual ~base(){}; // for polymorphism
};
struct left : base {};
struct right : base {};
struct derived : left, right {};

derived d;
left* l = &d;
right* r = dynamic_cast<right*>(l);

c) Otherwise, the runtime check fails . c)否则,运行时检查失败 If the dynamic_cast is used on pointers, the null pointer value of type new_type is returned .如果在指针上使用 dynamic_cast,则返回 new_type 类型的空指针值 If it was used on references, the exception std::bad_cast is thrown.如果它用于引用,则抛出异常 std::bad_cast。

Neither 5a nor 5b cases apply, so this "otherwise" case 5c does. 5a 和 5b 情况都不适用,所以这个“否则”情况 5c 不适用。


  1. e is same as type. e 与类型相同。 Sidecast?侧播?

Not a sidecast.不是侧倾。 A sidecast is explained in 5b.侧播在 5b 中解释。 Casting to same type is just an identity cast (rarely useful, so not a commonly used terminology either).转换为相同类型只是身份转换(很少有用,因此也不是常用术语)。


It may be that the conditions of the book attempt describe whether the conversion is well-formed.可能是本书尝试的条件描述了转换是否格式良好。 Although, "then the cast will succeed" certainly seems to imply more.虽然, “然后演员会成功”当然似乎意味着更多。 The quoted rules are not correct for describing whether the cast succeeds at runtime.引用的规则对于描述转换在运行时是否成功是正确的。

If the entire program is well-formed, then a compiler must compile the program.如果整个程序格式良好,则编译器必须编译该程序。 If an expression is ill-formed, then a compiler must give you a diagnostic message saying that you did wrong.如果表达式格式错误,那么编译器必须给您一条诊断消息,说明您做错了。

The example program that you've shown is well-formed and it must successfully compile.您展示的示例程序格式良好,必须成功编译。 It does compile on my system.它确实在我的系统上编译。

The last part is that the dynamic type of the object must match.最后一部分是对象的动态类型必须匹配。

Here, you have a B pointed to by an A pointer.在这里,您有一个A指针指向的B You are trying to dynamically cast the pointer to get a pointer to C , but there is no C to point to.您正在尝试动态转换指针以获取指向C的指针,但没有C指向。 So the cast fails.所以演员表失败了。

dynamic cast doesn't create objects, it just lets you access objects that are already there.动态转换不创建对象,它只是让您访问已经存在的对象。 When you call new B it creates a B object with an A subobject.当您调用new B它会创建一个带有A子对象的B对象。 It does not create a C object.它不创建C对象。

The issue is that the statement A* pa = new B;问题在于语句A* pa = new B; creates a B .创建一个B

But a B doesn't contain a C (downwards, upwards, or sideways), so the dynamic cast from pa to a C* will certainly fail.但是B不包含C (向下、向上或横向),因此从paC*的动态转换肯定会失败。

I'm, for the most part, adding another example of a side cast to previous answers…在大多数情况下,我在之前的答案中添加了另一个侧面演员的例子......

struct A {};
struct B { virtual ~B() = default; };
struct C : A, B {};

A *side_cast()
{
    B *obj = new C;
    return dynamic_cast<A *>(obj);
}

The above is a legal "side cast", and does not return null.以上是合法的“side cast”,不返回 null。 This shows that the target type neither :这表明目标类型既不是

  • has to be polymorphic.必须是多态的。
  • has to be related to the static type of * expression .必须与*表达式的静态类型相关。

for the cast to succeed at run time .为演员在运行时成功

The cast is well-formed , regardless of whether the type of the most-derived object pointed to by expression (IOW, the dynamic type of * expression ) inherits from the target type.无论表达式指向的派生最多的对象的类型(IOW, *表达式的动态类型)是否继承自目标类型,强制转换都是格式良好的 However, it will return static_cast<A *>(nullptr) unless A is a public and unambiguous base class of the dynamic type of * expression .但是,它将返回static_cast<A *>(nullptr)除非A* expression的动态类型的公共且明确的基类。 The gist of this is that you can legally write a whole bunch of nonsensical casts like dynamic_cast<std::tuple<int, float> *>(&std::cin) – note that std::cin is of type std::istream which is polymorphic –, but you'll simply get a null pointer at runtime.其要点是您可以合法地编写一大堆无意义的强制转换,例如dynamic_cast<std::tuple<int, float> *>(&std::cin) – 请注意std::cin的类型为std::istream这是多态的——但你只会在运行时得到一个空指针。

Simply put, a dynamic_cast between pointers can do most of the things that a static_cast can (except, at least, non-polymorphic downcast*), and, when the static type of * expression is polymorphic, it can also cast to any pointer to class whatsoever, except for removing cv-qualifers ( const or volatile ).简单地说,指针之间的dynamic_cast可以做static_cast可以做的大部分事情(至少,非多态向下转换*除外),并且,当*表达式的静态类型是多态时,它也可以转换到任何指向类,除了删除cv 限定符constvolatile )。 However, whether the cast actually returns non-null depends on the specific condition, checked at run time, mentioned above.但是,强制转换是否实际返回非 null 取决于特定条件,在运行时检查,如上所述。

* The reason why this is forbidden is that there is no safe way to do this, and dynamic_cast is expected to be safe. * 之所以禁止这样做,是因为没有安全的方法可以做到这一点,预计dynamic_cast是安全的。 Hence they make you write static_cast to make it clear that any ensuing UB is your fault.因此,他们让您编写static_cast以明确表示任何随后的 UB 都是您的错。

I would like to add my two cents on top of @eerorika 's fabulous answer, primarily focusing on the phrase of我想在@eerorika 的精彩回答之上加上我的两分钱,主要集中在

If only one object of Derived type is derived from the subobject pointed/identified by an expression, then....如果只有一个派生类型的对象是从表达式指向/标识的子对象派生的,那么......

I think this is to prevent downcasting given the following condition:我认为这是为了防止出现以下情况的向下转型:

           Base  
             |
          Derived
          /      \
         |        V 
         |      Right
          \      /
           V    V
         MostDerived

In this case, there 2 objects of Derived in MostDerived.在这种情况下,MostDerived 中有 2 个 Derived 对象。 Upon casting a Base pointer, which points to a MostDerived instance, to a Derived pointer, it is impossible for the compiler to know which of the Derived object one's referring to.在将指向 MostDerived 实例的 Base 指针转换为 Derived 指针时,编译器不可能知道它指的是哪个 Derived 对象。 (The one that is referred by MostDerived or the one that's referred by Right.) (由 MostDerived 引用的那个或由 Right 引用的那个。)

In fact, this is the exact case of ambiguous base access and will cause compiler warning or failed compilation depends on your implementation.事实上,这正是基址访问不明确的情况,会导致编译器警告或编译失败,具体取决于您的实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM