15

I was reading this and unfortunately could not understand in depth why the compiler does not allow conversion from Derived** to Base**. Also I have seen this which gives no more info than the parashift.com's link.

EDIT:

Let us analyze this code line by line:

   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()!
Community
  • 1
  • 1
Narek
  • 38,779
  • 79
  • 233
  • 389
  • There's a complete working example of why that cast is dangerous in the C++FAQ article. What more do you want? – Mat Nov 06 '11 at 09:03
  • 1
    key to understanding the FAQ's example is that in practice a polymorphic object contains a hidden pointer to a table of function pointers, which point to its virtual functions. so if you can make a `NuclearSubmarine*` point to a `Car` instead of `NuclearSubmarine`, then invoking a virtual `Car` function might instead invoke the sub's missile firing. with disastrous results. – Cheers and hth. - Alf Nov 06 '11 at 09:03
  • And, of course, there is no NuclearSubmarine::openGasCap function, so this code would call a nonexistent function with no casts or warnings if the proposed conversion were allowed. – David Schwartz Nov 06 '11 at 09:12

3 Answers3

23

It's basically the same reason why a bowl of bananas is not a bowl of fruits. If a bowl of bananas were a bowl of fruits, you could put an apple into the bowl, and it would no longer be a bowl of bananas.

As long as you only inspect the bowl, the conversion is harmless. But as soon as you start modifying it, the conversion becomes unsafe. This is the key point to bear in mind. (This is the precise reason why the immutable Scala collections actually allow the conversion, but the mutable collections prohibit it.)

Same with your example. If there was a conversion from Derived** to Base**, you could put a pointer to an apple were the type system promised only a pointer to a banana could exist. Boom!

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 1
    Great analogy. I'll have to steal^H^H^H^H^Huse that. "If you could convert a modifiable bowl of bananas into a modifiable bowl of fruit, you could put an apple into it and it would no longer be a bowl of bananas." – David Schwartz Nov 06 '11 at 09:21
  • 1
    @David: Actually, I stole it myself from the mighty [Jon Skeet](http://vimeo.com/17599365). – fredoverflow Nov 06 '11 at 09:28
  • Regarding to this: "If you could convert a modifiable bowl of bananas into a modifiable bowl of fruit, you could put an apple into it and it would no longer be a bowl of bananas." how in memory it is expressed that it is a bowl of bananas, and what kind of problems may occur if I use it for apples? – Narek Nov 08 '11 at 16:24
  • @Narek: Your question is moot, because there is no conversion from `Derived**` to `Base**`. You cannot treat a bowl of bananas as a bowl of fruits, and hence you cannot even try to put an apply into the bowl. The type system will trigger a compile-time error. – fredoverflow Nov 08 '11 at 18:13
  • It's like reverse Quantum Mechanics. – Puppy Jan 10 '12 at 20:47
  • 2
    Then why can't I cast a `Derived**` to `Base * const *`? The constness would prevent me from pointing a derived pointer to a different derived pointer. – Calmarius Oct 05 '18 at 16:17
11

There are no shortage of senseless errors this would permit:

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

Here is a full working example:

#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?
}

And here it is in action:

$ 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
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 1
    Not correct your classes here derive from different Base classes :P – Alok Save Nov 06 '11 at 09:11
  • Why is assigning `*m1` to `*m2` bad? At that stage all you are doing is assigning it to a `Musician*`. – Troubadour Nov 06 '11 at 09:25
  • Because `**f` is now a Pianist. – David Schwartz Nov 06 '11 at 09:29
  • I dont understand the example. "// Since p is a Flutist* this can't possibly play piano, can it?", but `p` is a `Pianist*`, it is supposed to play piano, it is playing piano, where is the problem? In `VeryBad` the `Flutist*` gets assigned a `Pianist*`, but if this causes a problem its either not demonstrated in the example or I completely missed the point – 463035818_is_not_an_ai May 08 '17 at 19:54
  • @tobi303 Sorry. Typo fixed. The code still produces the same output. Amazing this wasn't spotted in more than 5 years! – David Schwartz May 08 '17 at 20:23
  • 1
    no problem. I wasnt sure if there was something wrong with the example or with my understanding. I read a bit elsewhere and now it is more or less clear. Your example really helped a lot. – 463035818_is_not_an_ai May 08 '17 at 21:12
0

Vehicle** vehiclePtrPtr = carPtrPtr; is not allowed because it is a Derived** to Base** conversion, which is not allowed.

Reason why it is not allowed is illustrated in your example.

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

so that carPtrPtr points to a pointer to Car.

NuclearSubmarine sub; NuclearSubmarine* subPtr = ⊂

this is legal also, but if you could do

Vehicle** vehiclePtrPtr = carPtrPtr;

you could accidentaly do

*vehiclePtrPtr = subPtr;

that is *vehiclePtrPtr is a pointer to a Car. with this last line, you assign to it a pointer to a Sub. Thus, you could now call a method defined in derived class Sub on a object of type Car, with undefined behavior.

kiriloff
  • 25,609
  • 37
  • 148
  • 229