【问题标题】:Difference in usage of pointer downcast & upcast?指针向下转换和向上转换的使用差异?
【发布时间】:2012-10-08 12:11:55
【问题描述】:

我想知道当我们使用向下转换和向上转换时,指针转换到底发生了什么。 我有2个问题。其中前两个是评论。第三季度结束了。

#include<iostream>
using namespace std;
class A{
  public:
    virtual void  f()
    {
      cout<<"A"<<endl;
    }
};
class B: public A{
  public:
     virtual void   f()
    {
      cout<<"B"<<endl;
    }
};
int main()
{
  A* pa =new A();
  B* pb =new B();
  A* paUpcast= new B();
  /*
    Q1:
    Is the line above equivalent to the following?
    A* paUpcast = (A*) B;
  */
  B* pbDowncast=(B*)paUpcast;
  /*
    Q2:Why we cannot use the following 2 code;
    B* pbDowncast=new A();
    B* pbDowncast = (B*) pa;
  */
  pa->f();
  pb->f();
  paUpcast->f();
  pbDowncast->f();


  return 1;
}

Q3:我想总结一个规则来推断如果我们一起使用虚函数和指针转换会发生什么,但我就是想不通。

最初,我认为虚函数会引导我们到达指针真正指向的地方。 因此,当我们输入

A* paUpcast= new B();
paUpcast->f();

如果 A.f() 是虚函数,第二行会显示“B”,因为 paUpcast 实际上是指向 B 对象

但是,当我们输入

B* pbDowncast=(B*)pa;
pbDowncast->f();

它会显示“A”而不是“B”,这会导致矛盾发生。

谁能解释或给我一些提示?非常感谢

【问题讨论】:

    标签: c++ inheritance pointers casting virtual


    【解决方案1】:

    我会尝试解释我是如何理解它的。帮助我的提示是考虑乐高积木。

    在您的情况下,我们有两个乐高积木,一个名为 A,另一个名为 B... 但想象一下 B 积木是由两个积木组成的积木,其中一个积木是同类型A

     A     B
    +-+  +-+-+
    |a|  |a|b|
    +-+  +-+-+
    

    然后,您使用指针来引用每个乐高积木,但每个积木都有自己的形状,所以,想象一下:

    A* pa =new A();
    B* pb =new B();
    A* paUpcast= new B();
    
    A *pa -->       +-+   new A()
                    |a|
                    +-+
    B* pb -->       +-+-+ new B()
                    |a|b|
                    +-+-+
    A* paUpcast --> +-+-+ new B()
                    |a|b|
                    +-+-+
    

    注意paUpcast指针是A类型的指针,但是持有B类型的一块,B一块不同于A一块,你可以看到是一块比它的基数略大。

    这就是你所说的向上转换,基指针就像一个通配符,可以在继承树上向下保存任何相关内容。

    A* paUpcast= new B();

    上面的行是否等同于下面的行?

    A* paUpcast = (A*) B;

    好吧,假设你真的想写这个:A* paUpcast = (A*) new B(); 是的,它是。您创建B 类的新实例并将其存储到指向A 类的指针中,在分配到指针之前转换新实例不会改变它将被存储到基类指针中的事实。

    为什么我们不能使用下面2个代码;

    B* pbDowncast=new A();

    B* pbDowncast = (B*) A;

    记住乐高积木。执行B* pbDowncast=new A() 时会发生什么?:

    B* pbDowncast --> +-+ new A()
                      |a|
                      +-+
    

    创建一个新的基类实例并将其存储到派生类的指针中,如果您仔细观察乐高积木不合适,您会尝试将基类视为派生类! A 部分缺少B 类型所必需的额外内容;所有这些东西都“存储”到乐高积木的额外部分中,B = all the A stuff plus something more

      B
    +-+-----------------------------------+
    |a|extra stuff that only B pieces have|
    +-+-----------------------------------+
    

    如果您尝试调用只有B 类具有的方法会发生什么?拥有一个B 指针,您可以调用所有B 方法,但是您创建的实例来自A 类型,它没有B 方法,它不是用所有这些额外的方法创建的东西。

    但是,当我们键入时

    B* pbDowncast=(B*)pa;

    pbDowncast->f();

    显示“A”而不是“B”,导致矛盾发生。

    这对我来说并不矛盾,记住乐高积木,pa 指针指向A 类型的一块:

    A *pa --> +-+
              |a|
              +-+
    

    这篇文章缺少所有B 的东西,事实是缺少在标准输出上打印Bf() 方法......但它有一个打印f() 的方法A在输出上。

    希望对你有帮助!

    编辑:

    看来你也同意用downcast是不合适的吧?

    不,我不同意。向下转换根本不是不合适的,但根据它的用途它会是不合适的。与所有 C++ 工具一样,向下转换具有实用性和使用范围;所有尊重善用的诡计都是适当的。

    那么向下转换工具有什么用处呢?恕我直言,任何不会破坏代码或程序流程的东西,如果程序员知道他在做什么,则尽可能保持代码的可读性和(对我来说最重要)。

    向下转换采用可能的继承分支毕竟是一种常见的做法:

    A* paUpcast = new B();
    static_cast<B*>(paUpcast)->f();
    

    但是使用更复杂的继承树会很麻烦:

    #include<iostream>
    using namespace std;
    class A{
    public:
        virtual void  f()
        {
            cout<<"A"<<endl;
        }
    };
    class B: public A{
    public:
        virtual void   f()
        {
            cout<<"B"<<endl;
        }
    };
    class C: public A{
    public:
        virtual void   f()
        {
            cout<<"C"<<endl;
        }
    };
    
    A* paUpcast = new C();
    static_cast<B*>(paUpcast)->f(); // <--- OMG! C isn't B!
    

    要解决这个问题,您可以使用dynamic_cast

    A* paUpcast = new C();
    
    if (B* b = dynamic_cast<B*>(paUpcast))
    {
        b->f();
    }
    
    if (C* c = dynamic_cast<C*>(paUpcast))
    {
        c->f();
    }
    

    但是dynamic_cast 是众所周知的lack of performance,您可以研究dynamic_cast 的一些替代方案,例如内部对象标识符或转换运算符,但为了坚持这个问题,向下转换一点也不差如果使用得当。

    【讨论】:

    • 嘿,谢谢您的图表解释。看来你也同意用downcast是不合适的吧?
    • @paperBirdMaster:我在向上转换和向下转换中发现的最好的之一。谢谢!
    【解决方案2】:

    不要在 C++ 中使用 C 风格的强制转换!没有理由这样做,它会混淆你正在尝试做的事情的含义,更重要的是,如果你不投射你认为你正在投射的东西,它会产生有趣的结果。在上述情况下,您始终可以使用static_cast&lt;T*&gt;(),尽管所有向下转换可能最好是dynamic_cast&lt;T*&gt;(),而不是:后者只有在转换实际上意味着工作时才成功,在这种情况下它返回一个指向正确对象的指针,可能根据需要调整指针值(在涉及多重继承的情况下,指针值可能会改变)。如果dynamic_cast&lt;T*&gt;(x) 失败,即x 不是真正的指向T(或派生)类型对象的指针,则它返回null。

    现在,问题是:指针强制转换只会影响指针,不会影响对象。也就是说,指向对象的类型永远不会改变。在问题 1 的场景中,您创建一个指向派生类型的指针并将其分配给指向基类型的指针。由于派生类型 is-a 基类型,这种转换是隐式的,但它等效于

    A* paUpcast = static_cast<A*>(pb);
    

    在第二种情况下,您将paUpcast 转换为B*。由于paUpcast 是将B* 转换为A* 的结果,使用static_cast&lt;T*&gt;(x) 转换回B* 工作:当使用static_cast&lt;&gt;() 转换指针或引用时,您可以反转隐式转换的效果.如果您想以任何其他方式导航类层次结构,而不是反转隐式转换的效果,您需要使用dynamic_cast&lt;&gt;()。在这种情况下,您也可以使用 dynamic_cast&lt;&gt;(),但 dynamic_cast&lt;&gt;() 需要付出一些代价。

    现在,对于第三种情况,pa 实际上指向一个不是B 对象的A 对象。编译器将允许您将 pa 转换为 B*,但您在对编译器撒谎:pa 不是B* 隐式转换为 A* 的结果并使用static_cast&lt;B*&gt;(pa)(或(B*)pa,在这种情况下是等价的,但C风格的强制转换总是等价于static_cast&lt;&gt;()s)会导致未定义的行为:编译器会随心所欲想做。由于AB 对象的布局相似,它最终会调用A 的虚函数,但这只是许多可能结果之一,并不能保证会发生什么。如果您使用了dyanmic_cast&lt;B*&gt;(pa),结果将是一个空指针,表明从paB* 的转换不起作用。

    【讨论】:

    • 感谢您的解释。根据你所说,downcast在我的场景中似乎不合适,如果我在downcast中使用static_cast,结果将取决于编译器。
    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多