从派生**到基数**的转换

Conversion from Derived** to Base**

提问人:Narek 提问时间:11/6/2011 最后编辑:CommunityNarek 更新时间:5/9/2017 访问量:2335

问:

我正在阅读这篇文章,不幸的是,我无法深入理解为什么编译器不允许从 Derived** 转换为 Base**。我也看到了这一点,它提供的信息只比 parashift.com 的链接多。

编辑:

让我们逐行分析这段代码:

   Car   car;
   Car*  carPtr = &car;
   Car** carPtrPtr = &carPtr;
   //MyComment: Until now there is no problem!

   Vehicle** vehiclePtrPtr = carPtrPtr;  // This is an error in C++
   //MyComment: Here compiler gives me an error! And I try to understand why. 
   //MyComment: Let us consider that it was allowed. So what?? Let's go ahead!

   NuclearSubmarine  sub;
   NuclearSubmarine* subPtr = ⊂
   //MyComment: this two line are OK too!

   *vehiclePtrPtr = subPtr;

   //MyComment: the important part comes here... *vehiclePtrPtr is a pointer to
   //MyComment: a vehicle, particularly in our case it points to a Car object.
   //MyComment: Now when I assign to the pointer to the Car object *vehiclePtrPtr,
   //MyComment: a pointer to NuclearSubmarine, then it should just point to the
   //MyComment: NuclearSubmarine object as it is indeed a pointer to a Vehicle,
   //MyComment: isn't it? Where is my fault? Where I am wrong?

   // This last line would have caused carPtr to point to sub!
   carPtr->openGasCap();  // This might call fireNuclearMissle()!
C 继承 c++-faq 指针转换

评论

0赞 Mat 11/6/2011
在 C++FAQ 文章中有一个完整的工作示例来说明为什么该强制转换是危险的。你还想要什么?
1赞 Cheers and hth. - Alf 11/6/2011
理解 FAQ 示例的关键是,在实践中,多态对象包含指向函数指针表的隐藏指针,该指针指向其虚拟函数。因此,如果您可以指向 A 而不是 ,那么调用虚拟函数可能会调用潜艇的导弹发射。造成灾难性后果。NuclearSubmarine*CarNuclearSubmarineCar
0赞 David Schwartz 11/6/2011
当然,没有 NuclearSubmarine::openGasCap 函数,因此如果允许建议的转换,此代码将调用不存在的函数,而不会出现强制转换或警告。

答:

11赞 David Schwartz 11/6/2011 #1

不乏无意义的错误,这将允许:

class Flutist : public Musician
...

class Pianist : public Musician
...

void VeryBad(Flutist **f, Pianist **p)
{
 Musician **m1=f;
 Musician **m2=p;
 *m1=*m2; // Oh no! **f is supposed to be a Flutist and it's a Pianist!
}

下面是一个完整的工作示例:

#include <stdio.h>

class Musician
{
 public:
 Musician(void) { ; }
 virtual void Play(void)=0;
};

class Pianist : public Musician
{
 public:
 Pianist(void) { ; }
 virtual void Play(void) { printf("The piano blares\n"); }
};

class Flutist : public Musician
{
 public:
 Flutist(void) { ; }
 virtual void Play(void) { printf("The flute sounds.\n"); }
};

void VeryBad(Flutist **f, Pianist **p)
{
 Musician **m1=f;
 Musician **m2=p;
 *m1=*m2; // Oh no! **f is supposed to be a Flutist and it's a Pianist!
}

int main(void)
{
 Flutist *f=new Flutist();
 Pianist *p=new Pianist();
 VeryBad(&f, &p);
 printf("Mom is asleep, but flute playing wont bother her.\n");
 f->Play(); // Since f is a Flutist* this can't possibly play piano, can it?
}

这是它的实际应用:

$ g++ -fpermissive verybad.cpp -o verybad
verybad.cpp: In function void VeryBad(Flutist**, Pianist**):
verybad.cpp:26:20: warning: invalid conversion from Flutist** to Musician** [-fpermissive]
verybad.cpp:27:20: warning: invalid conversion from Pianist** to Musician** [-fpermissive]
$ ./verybad 
Mom is asleep, but flute playing wont bother her.
The piano blares

评论

1赞 Alok Save 11/6/2011
不更正您的类在此处派生自不同的基类:P
0赞 Troubadour 11/6/2011
为什么分配给不好?在那个阶段,您所做的只是将其分配给 .*m1*m2Musician*
0赞 David Schwartz 11/6/2011
因为现在是一名钢琴家。**f
0赞 463035818_is_not_an_ai 5/9/2017
我不明白这个例子。“// 既然 p 是长笛演奏家*,这不可能弹钢琴,对吧?”,但是一个,它应该弹钢琴,它在弹钢琴,问题出在哪里?在分配了一个 ,但如果这导致了一个问题,它要么没有在示例中演示,要么我完全错过了这一点pPianist*VeryBadFlutist*Pianist*
1赞 463035818_is_not_an_ai 5/9/2017
没关系。我不确定这个例子或我的理解是否有问题。我在其他地方读过一点,现在或多或少很清楚了。你的例子真的有很大帮助。
23赞 fredoverflow 11/6/2011 #2

这基本上与一碗香蕉不是一碗水果的原因相同。如果一碗香蕉一碗水果,你可以把一个苹果放进碗里,它就不再是一碗香蕉了。

只要你只检查碗,转换是无害的。但是一旦你开始修改它,转换就会变得不安全。这是要牢记的关键点。(这就是为什么不可变的 Scala 集合实际上允许转换,但可变集合禁止转换的确切原因。

与您的示例相同。如果有从 到 的转换,你可以放置一个指向苹果的指针,如果类型系统承诺只能存在一个指向香蕉的指针。繁荣!Derived**Base**

评论

1赞 David Schwartz 11/6/2011
很好的类比。我必须偷^H^H^H^H^H^H使用它。“如果你能把一碗可修改的香蕉变成一碗可修改的水果,你可以把一个苹果放进去,它就不再是一碗香蕉了。
1赞 fredoverflow 11/6/2011
@David:实际上,我自己从强大的乔恩·斯基特那里偷来的。
0赞 Narek 11/9/2011
关于这一点:“如果你能把一碗可修改的香蕉变成一碗可修改的水果,你可以把一个苹果放进去,它就不再是一碗香蕉了。 在记忆中如何表示它是一碗香蕉,如果我用它来做苹果,可能会出现什么样的问题?
0赞 fredoverflow 11/9/2011
@Narek:你的问题没有实际意义,因为没有从 到 的转换。你不能把一碗香蕉当成一碗水果,因此你甚至不能尝试在碗里涂上药。类型系统将触发编译时错误。Derived**Base**
2赞 Calmarius 10/6/2018
那为什么我不能投一个呢?恒定性将阻止我将派生指针指向其他派生指针。Derived**Base * const *
0赞 kiriloff 5/5/2013 #3

Vehicle** vehiclePtrPtr = carPtrPtr;不允许,因为它是不允许的 to 转换。Derived**Base**

示例中说明了不允许这样做的原因。

   Car   car;
   Car*  carPtr = &car;
   Car** carPtrPtr = &carPtr; 

因此,它指向指向 的指针。carPtrPtrCar

核潜艇; 核潜艇* subPtr = ⊂

这也是合法的,但如果你能做到的话

Vehicle** vehiclePtrPtr = carPtrPtr;

你可能会不小心这样做

*vehiclePtrPtr = subPtr;

即是指向 .使用最后一行,您可以为其分配一个指向 .因此,您现在可以在具有未定义行为的对象上调用派生类中定义的方法。*vehiclePtrPtrCarSubSubCar