Преобразование из производных ** в базовые **

Я читал это и, к сожалению, не смог понять в глубина, почему компилятор не разрешает преобразование из 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()!

person Narek    schedule 06.11.2011    source источник
comment
Полный рабочий пример того, почему это приведение опасно, можно найти в статье часто задаваемых вопросов по C ++. Что вы еще хотите?   -  person Mat    schedule 06.11.2011
comment
Ключом к пониманию примера из FAQ является то, что на практике полиморфный объект содержит скрытый указатель на таблицу указателей функций, которые указывают на его виртуальные функции. поэтому, если вы можете указать NuclearSubmarine* на Car вместо NuclearSubmarine, тогда вызов виртуальной Car функции может вместо этого вызвать запуск ракеты субмарины. с плачевными результатами.   -  person Cheers and hth. - Alf    schedule 06.11.2011
comment
И, конечно же, нет функции NuclearSubmarine :: openGasCap, поэтому этот код вызывал бы несуществующую функцию без приведений или предупреждений, если бы предлагаемое преобразование было разрешено.   -  person David Schwartz    schedule 06.11.2011


Ответы (3)


По сути, это та же самая причина, по которой миска с бананами - это не миска с фруктами. Если бы миска с бананами была миской с фруктами, вы могли бы положить в нее яблоко, и это уже не была бы миска с бананами.

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

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

person fredoverflow    schedule 06.11.2011
comment
Отличная аналогия. Мне придется украсть ^ H ^ H ^ H ^ H ^ Huse это. Если бы вы могли превратить модифицируемую миску с бананами в модифицируемую миску с фруктами, вы могли бы положить в нее яблоко, и это уже не была бы миска с бананами. - person David Schwartz; 06.11.2011
comment
@David: На самом деле, я сам украл его у могучего Джона Скита. - person fredoverflow; 06.11.2011
comment
В связи с этим: если бы вы могли превратить изменяемую миску с бананами в изменяемую миску с фруктами, вы могли бы положить в нее яблоко, и это уже не была бы миска с бананами. как в памяти выражается, что это миска с бананами, и какие проблемы могут возникнуть, если я использую ее для яблок? - person Narek; 08.11.2011
comment
@Narek: Ваш вопрос спорный, потому что нет преобразования из Derived** в Base**. Вы не можете относиться к миске бананов как к миске с фруктами, и, следовательно, вы не можете даже пытаться положить в миску аппликатор. Система типов вызовет ошибку времени компиляции. - person fredoverflow; 08.11.2011
comment
Это похоже на обратную квантовую механику. - person Puppy; 11.01.2012
comment
Тогда почему я не могу разыграть Derived** на Base * const *? Константность помешала бы мне указать производный указатель на другой производный указатель. - person Calmarius; 05.10.2018

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

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
person David Schwartz    schedule 06.11.2011
comment
Не исправляйте, что ваши классы здесь происходят из разных базовых классов: P - person Alok Save; 06.11.2011
comment
Почему присваивать *m1 *m2 плохо? На этом этапе все, что вы делаете, - это присваиваете его Musician*. - person Troubadour; 06.11.2011
comment
Потому что **f теперь пианист. - person David Schwartz; 06.11.2011
comment
Я не понимаю этого примера. // Поскольку p - флейтист *, он не может играть на пианино, не так ли ?, но p это Pianist*, он должен играть на пианино, он играет на пианино, в чем проблема? В VeryBad Flutist* присваивается Pianist*, но если это вызывает проблему, это либо не показано в примере, либо я полностью упустил суть - person 463035818_is_not_a_number; 08.05.2017
comment
@ tobi303 Извините. Опечатка исправлена. Код по-прежнему дает тот же результат. Удивительно, но этого не было замечено более 5 лет! - person David Schwartz; 08.05.2017
comment
нет проблем. Я не был уверен, что что-то не так с этим примером или с моим пониманием. Прочитал немного в другом месте и теперь более-менее понятно. Ваш пример действительно очень помог. - person 463035818_is_not_a_number; 09.05.2017

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

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

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

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

Атомная подводная лодка; Ядерная подводная лодка * subPtr =

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

Vehicle** vehiclePtrPtr = carPtrPtr;

ты мог случайно сделать

*vehiclePtrPtr = subPtr;

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

person kiriloff    schedule 04.05.2013