基类指针可以指向派生类对象。为什么反之亦然?


77

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

Answers:


155

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

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

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

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

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

在第一种情况下,您知道它是狗,可以给我买皮带,每个人都很高兴。

在第二种情况下,我没有告诉您我的宠物是什么,因此,无论如何如果您要给我买礼物,您需要知道我没有告诉过您的信息(或只是猜测),如果您给我买了皮带,原来我真的有一只狗,每个人都很高兴。

但是,如果我实际上有一只猫,那么我们现在知道您做了一个错误的假设(投射),并且在皮带上有一只不快乐的猫(运行时错误)。

ClassCastException猫


8
+1:到目前为止,最佳答案,恕我直言。一个简单的类比可以直观地说明问题。
奥利弗·查尔斯沃思

3
“对于每个问题,都有一个简单,整洁和错误的解决方案。” HL Mencken。直觉的答案通常属于此类,特别是用“现实世界”术语描述的面向对象。有时基类指针确实指向派生类类型的对象,这就是为什么(正如问题所指出的)可以强制转换。
弗雷德·纽克

1
@Fred:答案没有错;指向带有派生指针的基类对象永远是无效的。正如您所说,将基类指针强制转换为派生指针有时是有效的。
奥利弗·查尔斯沃思

3
狗是动物,但动物不一定是狗,它可能是长颈鹿。这个比喻说明了什么?如果认为动物是基类,而狗/长颈鹿是派生类。由于狗/长颈鹿确实是动物,即子类应该能够指向基类?不应该这样吗?
Zuzu

1
是的,如果您要强制转换,则您是在明确告诉编译器关闭cos,您却拥有其他信息,而没有。我想我可以扩大动物比喻添加指针和铸件一点-包涵
JK。

12

我们有两个对象。

class A {
   int a;
};

class B : A {
   int b;
};

分配的实例B。我们可以用接口或者一个A*或一个B*

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


11

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

当您拥有有效的指向类型的指针时,则表示所指向的对象将在某些位置具有某些数据,以便我们可以找到它。如果您有一个指向派生对象的指针,则可以保证所指向的对象包含派生对象的所有数据成员,但是当您指向Base时,它实际上并没有该对象和Bad Things Happen™。

但是,派生保证将所有基本数据成员都放在同一位置。这就是为什么指向Base的指针实际上可以指向Derived的原因。


1
所有派生类都不“保证所有基本数据成员都位于同一位置”。
弗雷德·纽克

相对于上述指针,我相信它们是。
小狗

@Bo:那样只会使类型转换和函数调用更加复杂。从根本上讲,基于指针的继承纯粹是基于当我有一个指向Base的指针时,vtable和member变量存在于它们在Base的常规实例中的位置。
小狗

6

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

不建议将类型强制转换为派生类:如果尝试访问不属于基类的成员,会发生什么情况?


清晰的解释
Chandra Shekhar,

3

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

    Animal * pAnimal = new Tiger();

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

    PoisonDartFrog * pPoisonDartFrog = new GenericFrog();

@DeadMG:您在编辑中捉住了我。我认为一个例子有助于传达这一概念。
安迪·托马斯

1
三年后的今天,两个拒绝投票的人是否愿意就他们认为此答案有缺陷的内容发表评论?
安迪·托马斯

...不一定是物体是毒箭蛙...
Aryaman

@Aryaman-在没有人将GenericFrog定义为PoisonDartFrog的子类的合理假设下,这肯定不是事实。
安迪·托马斯

2
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

1

因为C ++是一种静态类型的语言,所以允许隐式的从基数到派生的转换将破坏类型系统。Bjarne Stroustrup不需要任何“无法理解的消息”运行时错误。


当Bjarne Stroustrup开始使用类编写C语言时,C甚至无法对函数调用进行类型检查:)但是,是的,C是静态类型的,尽管不是很强。
fredoverflow

1
这就是为什么我不理解以静态类型来防止指针转换是合理的。有很多事情在运行时会出错(“消息无法理解”吗?),例如取消引用空指针,而Stroustrup的目标似乎与在运行时信任程序员的方向相反。
弗雷德·纽克

@Fred:也许您从未接触过动态类型的语言,我不知道。如果不是静态类型的C ++,您可以说p->any_method_name(42, "hello", test);,如果存在接受三个参数的方法,则编译器将不会在编译时进行任何检查,因为他不知道p指向类的实例静态已知类型的。检查将推迟到运行时,这会花费更多,并且显然不可靠(但从正面看,更灵活-例如,您不需要子类型化或泛型)。
fredoverflow

我知道Python和其他语言。
弗雷德·纽克

1

简短的答案

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

0

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

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.

至于原因:一般而言,基本指针比派生指针更通用。因此,它对继承类型的了解较少。不能仅将派生指针分配给基本类型的指针,而不能进行强制转换,因为它无法判断基本指针是Derived类型还是其派生类型之一。


那里的示例可以帮助您说明原因,此外,如果您不愿意阅读注释,您会注意到它的确是为什么。在提供帮助之后,添加了一些说明。
Washu 2011年

2
@ AndyThomas-Cramer:与另一个用户进行投票是违反系统精神的,我很震惊地看到一个4k代表用户如此公然地提倡这种滥用。
Fred Nurk,2011年

@Fred Nurk-我的目的并不是要建议一个现状。我的投票是无条件的。
安迪·托马斯

0

如果将地址从基类指针分配到派生类指针,则可以将基类对象分配给派生类指针。如果没有派生类,则冒着访问派生类成员的风险。尽管派生类方法可以在基类上工作,但只有在该方法不访问派生类成员数据的情况下,它们才能这样做。

那是巨大的风险。

因此,我们强迫您进行强制转换,以便您必须承认所声明的免责声明(您可能会犯一个愚蠢的错误,请小心)。


0

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

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

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

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

那么我们期望在D指向的地址处有一个派生类型对象,并且如果在那里获得基本类型对象,则由于基本类型不是派生类型,我们将无法从该基本类型对象访问派生类信息。


0

行动胜于语言。孩子也可以拥有父类对象。如果您对指针的理解很好,那么您就不受限制了。在下面的代码中,子类指针(具有Parent类对象)打印了两个值。还通过打印证明了自己的地址。欢迎任何建议!

#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 

0

通常,一种类型的指针不能指向另一种类型的对象。但是,此规则有一个重要例外,该例外仅与派生类相关。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.