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

Posted

技术标签:

【中文标题】从派生**到基础**的转换【英文标题】:Conversion from Derived** to Base** 【发布时间】:2011-11-06 08:59:39 【问题描述】:

我正在阅读this,不幸的是无法深入理解为什么编译器不允许从 Derived** 转换为 Base**。我还看到this 提供的信息不超过 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++FAQ 文章中有一个完整的工作示例说明了为什么该强制转换是危险的。你还想要什么? 理解FAQ示例的关键是,在实践中,多态对象包含一个指向函数指针表的隐藏指针,该指针指向它的虚函数。因此,如果您可以将NuclearSubmarine* 指向Car 而不是NuclearSubmarine,那么调用虚拟Car 函数可能会调用潜艇的导弹发射。结果是灾难性的。 当然,没有 NuclearSubmarine::openGasCap 函数,因此如果允许建议的转换,此代码将调用不存在的函数,没有强制转换或警告。 【参考方案1】:

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

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

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

【讨论】:

很好的比喻。我得偷^H^H^H^H^Huse那个。 “如果你能把一碗可变形的香蕉变成一碗可变形的水果,你可以把一个苹果放进去,它就不再是一碗香蕉了。” @David:实际上,我是从强大的Jon Skeet 那里偷来的。 关于这个:“如果你能把一碗可变形的香蕉变成一碗可变形的水果,你可以把一个苹果放进去,它就不再是一碗香蕉了。”一碗香蕉在记忆中是如何表达的,如果用在苹果上会出现什么样的问题? @Narek:你的问题没有实际意义,因为没有从Derived**Base** 的转换。你不能把一碗香蕉当成一碗水果,因此你甚至不能试着往碗里放一层。类型系统会触发编译时错误。 那为什么我不能将Derived** 转换为Base * const *?常量会阻止我将派生指针指向不同的派生指针。【参考方案2】:

这将允许出现无意义的错误:

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

【讨论】:

不正确你的类在这里派生自不同的基类:P 为什么将*m1 分配给*m2 不好?在那个阶段,您所做的只是将其分配给Musician* 因为**f 现在是钢琴家了。 我不明白这个例子。 "// 既然 p 是长笛演奏家*,这不可能弹钢琴吧?",但是pPianist*,它应该弹钢琴,它正在弹钢琴,问题出在哪里?在VeryBad 中,Flutist* 被分配了一个Pianist*,但是如果这导致了问题,它要么在示例中没有说明,要么我完全错过了重点 没问题。我不确定这个例子或我的理解是否有问题。我在别处读了一点,现在或多或少都清楚了。你的例子真的很有帮助。【参考方案3】:

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

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

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

这样carPtrPtr 指向一个指向Car 的指针。

核潜艇潜艇; 核潜艇* subPtr = ⊂

这也是合法的,但如果可以的话

Vehicle** vehiclePtrPtr = carPtrPtr;

你可能会不小心这样做

*vehiclePtrPtr = subPtr;

*vehiclePtrPtr 是指向Car 的指针。在最后一行中,您为其分配了一个指向Sub 的指针。因此,您现在可以在 Car 类型的对象上调用派生类 Sub 中定义的方法,但行为未定义。

【讨论】:

以上是关于从派生**到基础**的转换的主要内容,如果未能解决你的问题,请参考以下文章

为啥从指向基址的指针到指向派生的指针的静态转换“无效”?

C++ 类型将基础对象转换为派生对象

从基类到不同派生类的多重继承转换

c# 基础 object ,new操作符,类型转换

将派生类转换为基类

java--基本数据类型的转换(自动转换)