Преобразование из производного ** в базу ** - PullRequest
13 голосов
/ 06 ноября 2011

Я читал это и, к сожалению, не смог глубоко понять, почему компилятор не позволяет преобразование из Derived ** в Base **. Также я видел этот , который дает не больше информации, чем ссылка на parashift.com.

EDIT:

Давайте проанализируем этот код строка за строкой:

   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()!

Ответы [ 3 ]

20 голосов
/ 06 ноября 2011

Это в основном та же самая причина, почему ваза с бананами - это не ваза с фруктами.Если бы миска с бананами была миской с фруктами, вы могли бы положить яблоко в миску, и это больше не было бы миской с бананами.* осмотрите чашу, конверсия безвредна.Но как только вы начинаете модифицировать , преобразование становится небезопасным.Это ключевой момент, который нужно иметь в виду.(Это точная причина, по которой неизменные коллекции Scala действительно позволяют преобразование, но изменяемые коллекции запрещают его.)

То же самое с вашим примером.Если бы произошло преобразование из Derived** в Base**, вы могли бы поместить указатель на яблоко, если бы система типов обещала, что может существовать только указатель на банан.Boom!

11 голосов
/ 06 ноября 2011

Нет недостатка в бессмысленных ошибках, которые позволили бы:

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
0 голосов
/ 05 мая 2013

Vehicle** vehiclePtrPtr = carPtrPtr; не допускается, потому что это преобразование Derived** в Base**, что недопустимо.

Причина, по которой это запрещено, проиллюстрирована на вашем примере.

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

, так что carPtrPtr указывает на указатель на Car.

NuclearSubmarine sub;NuclearSubmarine * subPtr = ⊂

это также законно, но если бы вы могли сделать

Vehicle** vehiclePtrPtr = carPtrPtr;

, вы можете случайно сделать

*vehiclePtrPtr = subPtr;

, то есть *vehiclePtrPtrуказатель на Car.с помощью этой последней строки вы назначаете ей указатель на Sub.Таким образом, теперь вы можете вызывать метод, определенный в производном классе Sub, для объекта типа Car с неопределенным поведением.

...