【问题标题】:A Base Class pointer can point to a derived class object. Why is the vice-versa not true?基类指针可以指向派生类对象。为什么反之不成立?
【发布时间】:2011-06-23 15:47:06
【问题描述】:

基类指针可以指向派生类对象。为什么如果没有强制转换,反之亦然? 从逻辑上讲,基类没有足够的派生类信息,但派生类也应该具有基类的信息。 我在这里缺少一些基础知识。

【问题讨论】:

    标签: c++


    【解决方案1】:

    如果我告诉你我有一只狗,你可以放心地假设我有一只宠物。

    如果我告诉你我有一只宠物,你不知道那只动物是不是狗,它可能是一只猫,甚至可能是一只长颈鹿。如果不知道一些额外的信息,你不能安全地假设我有一只狗。

    类似地,派生对象是基类对象(因为它是子类),所以它可以被基类指针指向。但是,基类对象不是派生类对象,因此不能将其分配给派生类指针。

    (你现在听到的吱吱声是类比拉伸)

    假设你现在想给我的宠物买礼物。

    在第一个场景中你知道它是一只狗,你可以给我买一条皮带,每个人都很开心。

    在第二种情况下,我没有告诉你我的宠物是什么,所以如果你要给我买礼物,你需要知道我没有告诉你(或只是猜测)的信息,你给我买一条皮带,如果事实证明我真的养了一只狗,每个人都很高兴。

    但是,如果我真的养了一只猫,那么我们现在知道你做了一个错误的假设(演员表)并且牵着一只不开心的猫(运行时错误)。

    【讨论】:

    • +1:迄今为止最好的答案,恕我直言。一个简单的类比,直观地说明了问题。
    • “对于每一个问题,都有一个简单、简洁且错误的解决方案。” — H.L. 门肯。直观的答案通常属于这一类,尤其是用“现实世界”术语描述的 OO。有时基类指针确实指向派生类类型的对象,这就是为什么(正如问题所指出的那样)您可以强制转换。
    • @Fred:答案没有错;用指向派生的指针指向基类 object 永远是无效的。但是,正如您所说,将基类 pointer 强制转换为指向派生的指针有时是有效的。
    • 狗是动物,但动物不一定是狗,它可能是长颈鹿。这个比喻说明了什么?如果 Animal 被认为是基类,而 Dog/Giraffe 是派生类。既然 Dog/Giraffe 确实是动物,即子类应该能够指向基类?不应该是这样吗?
    • 是的,如果你正在强制转换,那么你就是在明确告诉编译器关闭,因为你有额外的信息,它没有。我想我可以扩展动物隐喻来添加指针和铸造 - 请耐心等待
    【解决方案2】:

    我们有两个对象。

    class A {
       int a;
    };
    
    class B : A {
       int b;
    };
    

    分配B 的实例。我们可以通过A*B* 与它进行交互。

    分配A 的实例。如果我们将其转换为B*,是否应该为成员b分配空间?

    【讨论】:

    • 这个!这帮助我清除了我对为什么不允许以技术方式使用它的疑问。谢谢。
    • @BillLynch B b1; A *pa; 声明 B 的对象和对象 A 的 ptr,现在我们必须像 pa=(A*)&b1; 这样 cast 或者我们可以直接使用 @ 987654331@?
    【解决方案3】:

    呃,因为基类不是派生类。

    当你有一个指向一个类型的有效指针时,那么你就是说指向的对象将在某些位置具有某些数据,以便我们可以找到它。如果你有一个指向派生对象的指针,那么你保证指向的对象包含派生的所有数据成员——但是当你指向一个 Base 时,它​​实际上没有那个,而且 Bad Things Happen™。

    但是,Derived 保证所有 Base 数据成员都位于相同的位置。这就是为什么指向 Base 的指针实际上可以指向 Derived。

    【讨论】:

    • 并非所有派生类都“保证所有 Base 数据成员都位于同一位置。”
    • 相对于有问题的指针,我相信它们是。
    • @Bo:这只会使转换和函数调用等事情变得更加复杂。从根本上说,基于指针的继承纯粹是基于当我有一个指向 Base 的指针时,vtable 和成员变量存在于它们将存在于 Base 的常规实例中的位置。
    【解决方案4】:

    因为派生类包含基类中的所有内容。但是基类并不包含派生类中的所有内容。

    不建议将基类类型转换为派生类:如果您尝试访问不属于基类的成员会怎样?

    【讨论】:

    • 明确说明
    【解决方案5】:

    这是有效的,因为老虎是动物:

        Animal * pAnimal = new Tiger();
    

    这是无效的,因为对象不是毒箭蛙。

        PoisonDartFrog * pPoisonDartFrog = new GenericFrog();
    

    【讨论】:

    • @DeadMG:你在编辑过程中抓住了我。我认为一个例子有助于传达这个概念。
    • 三年后,今天的两位反对者是否愿意评论他们认为这个答案有缺陷的地方?
    • ...对象是毒箭蛙并非必然正确...
    • @Aryaman - 这肯定不是真的,合理的假设是没有人将 GenericFrog 定义为 PoisonDartFrog 的子类。
    • @AbhishekMane - 这是一种普通的青蛙——青蛙的超类——而不是像 PoisonDartFrog 这样的青蛙的特定子类。
    【解决方案6】:

    因为 C++ 是一种静态类型语言,并且允许隐式 Base-to-Derived 转换会破坏类型系统。 Bjarne Stroustrup 不希望出现任何“消息无法理解”的运行时错误。

    【讨论】:

    • 在 Bjarne Stroustrup 开始使用类在 C 上工作时,C 甚至无法对函数调用进行类型检查 :) 但是,C 是静态类型的,尽管不是很强。
    • 这就是为什么我不明白用静态类型来证明防止指针转换是合理的。在运行时有很多事情可能出错(“消息不理解”?),例如取消引用空指针,而 Stroustrup 的目标似乎与在运行时信任程序员相反。
    • @Fred:也许你从未接触过动态类型语言,我不知道。如果C++不是静态类型的,你可以说p->any_method_name(42, "hello", test);,如果有这样一个接受三个参数的方法,编译器不会在编译时做任何检查,因为他不知道p指向一个实例属于静态已知类型的类。检查将推迟到运行时,这更昂贵且显然不太可靠(但从好的方面来说,更灵活——例如,您不需要子类型或泛型)。
    • @AbhishekMane 因为向上转型总是有效的(每只狗都是动物),但向下转型可能会失败(并非每只动物都是狗)。如果您将狗向上推向动物,然后通过static_cast 将其向下推向猫,则会出现未定义的行为。如果你通过dynamic_cast 向下转换,你会得到nullptr
    • @AbhishekMane 一个流行的例子是 vector<unique_ptr<Animal>> 包含指向猫的指针和指向狗的指针。
    【解决方案7】:
    class Base
    {
    public:
        int a;
    }
    
    class Derived : public Base
    {
    public:
        float b;
    }
    
    Base * pBase = new Base();
    pBase->a = 7; // setting the value of a in the base
    
    // make a pDerived that points to the SAME DATA as pBase
    Derived * pDerived = pBase;
    pDerived->a = 5; // this would be okay, base has a public member 'a'
    pDerived->b = 0.2f; // error pBase has no data member b and pDerived
                        // points to the SAME DATA as pBase
    

    【讨论】:

      【解决方案8】:

      简短的回答

      class A{
          public: 
              method1();
      };
      
      class B: public A{
          public: 
              method2();
      };
      
      
      int main(){
      
      // Case 1
      A* ptr_base = new B();
      // Here I can call all the methods in A by ptr_base even though it is assigned B ...
      // ... because B is derived from A and has all the information about methods of A
      // Case 2
      B* ptr_derived = new A(); // this will cause error
      // Now here ptr_derived is assigned information of A ...
      // ... So with this information can I call (*ptr_derived).method2(); ?...
      // ... the answer is No because A does not have information of method2() ...;
      // ... thus this declaration loses its meaning and hence error.
      return 0;
      }
      

      【讨论】:

        【解决方案9】:

        这是因为,“指针的类型就是指针指向的对象的类型”。所以,

        1. 如果我们有基类型指针 (*B):

        然后我们期望 B 指向的地址处有一个基类型对象(并希望访问其功能),如果我们在该地址获得派生类型对象,那么我们也能够访问所需的功能。这是因为派生类型是基类型。

        1. 如果我们有派生类型指针 (*D):

        然后我们期望 D 指向的地址处有一个派生类型对象,如果我们在那里获得基类型对象,那么我们将无法从基类型对象访问派生类信息,因为基类型不是派生类型.

        【讨论】:

          【解决方案10】:

          行动胜于雄辩。 Child 也可以有 Parent Class 对象。 如果您很好地理解了指针,那么您就会受到限制 在下面的代码中,通过子类指针(具有父类对象)打印了两个值。 通过打印他们的地址也证明了这一点。 欢迎提出任何建议!

          #include<iostream>
          using namespace std;
          class Baap{
              public:
                  int a;
                  void hoo(){ cout<<"hoo\n"; cout<<a;}
          };
          
          class Beta:public Baap{
              public:
                  int a;
                  int b;
                  void hee(){ cout<<"hee\n"; }
          };
          
          int main(){
              Baap baap;
              baap.a=1;
              Beta *beta=(Beta*)&baap;
              baap.a=3;
              beta->hee();
              beta->hoo();
              cout<<"\n beta = "<<beta<<"\n&baap = "<<&baap;
              return 0;
          }
          //output
           hee                                                                                                                           
           hoo                                                                                                                           
           3                                                                                                                             
            beta = 0x7ffd11dd3834                                                                                                        
           &baap = 0x7ffd11dd3834 
          

          【讨论】:

          • 问题是为什么我们需要通过(beta*) 进行类型转换,同时将address of base class object 传递给derived class ptr,而将passing address of derived class object 传递给base class ptr 我们这样做不需要类型转换(尽管我们可以使用它是另一部分)
          • 我同意这个问题应该是另一个部分。
          • 关于演员的问题。只有一个基本概念的提醒才能回答你的问题。 “ 父级总是在它的子级之前初始化内存。所以如果你试图在子指针中拥有基对象。那么你试图违反你告诉编译器你应该在基类对象之前知道对象的大小。所以编译器不允许它。因为任何主体都可能违反并使编译器在使用 new 运算符的运行时分配的情况下无法确定。只需尝试验证
          【解决方案11】:

          因为基类指针可以指向基类的实例或任何派生类型。派生指针只能指向该派生类型或其任何子类。

          struct Base {};
          struct Derived : Base {};
          struct Derived2 : Base {};
          Base* p = new Derived(); //Fine, Derived inherits from Base
          Derived* d = new Base(); //Not fine, Base is not an instance of nor derived from Derived.
          Derived* d2 = new Derived2(); // Also not fine, Derived2 derives from Base, but is not related to Derived.
          

          就原因而言:一般来说,基指针比派生指针更通用。因此,它对继承的类型知之甚少。派生指针不能在不进行强制转换的情况下被分配一个指向基类型的指针,因为它无法判断基指针是派生类型还是它的一个子类型。

          【讨论】:

          • 该示例有助于说明原因,此外,如果您费心阅读 cmets,您会注意到它确实说明了 WHY。在帮助点之后添加了一些说明。
          • @AndyThomas-Cramer:与其他用户交换赞成票违反了系统的精神,看到一个 4k 代表用户如此公然鼓吹这种滥用行为,我感到很震惊。
          • @Fred Nurk - 我无意提出交换条件。我的支持是无条件的。
          【解决方案12】:

          如果将基类指针的地址分配给派生类指针,则可以潜在地将基类对象分配给派生类指针。当您没有派生类时,您冒着访问派生类成员的风险。虽然派生类方法可以在基类上工作,但它们只有在方法不访问派生类成员数据时才会这样做。

          这是一个巨大的风险。

          所以我们强制你投,所以你必须承认免责声明(你可能会犯愚蠢的错误,请小心)。

          【讨论】:

          • "如果将base class pointer 的地址分配给派生类指针" 我认为应该将base class object 的地址分配给派生类指针
          【解决方案13】:

          一般来说,一种类型的指针不能指向不同类型的对象。然而,这条规则有一个重要的例外,它只与派生类有关。

          在这种情况下,BASE* 类型的指针可能指向Derived 类型的对象,即基类指针可以指向派生类对象,但反之亦然,因为基对象不是它的子对象类对象。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-06-16
            • 2011-04-06
            • 2012-03-14
            • 1970-01-01
            • 1970-01-01
            • 2017-06-17
            • 2014-12-05
            • 2021-07-02
            相关资源
            最近更新 更多