06 июля 2020

#13 Отсутствие автоматического преобразования указателей на указатель в указатель на указатель

В посте #6 Указатели на указатели и ссылки на указатели упоминалось, что указатели на указатели на производный класс не могут быть автоматически быть преобразованы на указатели на указатели на базовый класс. Кстати, это же и касается и случаев с увеличением количества указуемых объектов. Но почему? Что тут такого кардинально отличающегося от обычных указателей? Ведь для обычных указателей доступна такая конвертация.
Ответ начнём с того, что для стандартных типов, поддерживаемых языком, не так уж и много доступных конвертаций. Ещё меньше их для указателей. Для указателей доступно только автоматическое конвертирование из указателя на производный класс на указатель на базовый класс. Это логично. Также доступно и автоматическое ковертирование в void*. Что тоже необходимо для поддержки этой специфической идеи. Это весь (!) список доступных конвертаций. Жаловаться не приходится. Если бы интересующая нас конвертация не нарушала бы один из основополагающих принципов строгой типизации нашего с вами любимого языка, то её бы включили тоже, уж будьте уверены. Итак, перейдём непосредственно к объяснению причины:


Представим, что у нас существует такая иерархия классов.
Разрешив указателю на указатель на базовый класс присваивать в качестве значения указатель на указатель на производный класс, мы сможем разыменовать указатель на указатель на базовый класс, получив простой указатель на базовый класс, а вместе с этим позволим:
  • указателю одного из наследников присваивать указатель на другого наследника;
  • потерять информацию о производном классе, присвоив указателю на базовый класс объект базового класса.
Car* pc = new Car;
Car** ppc = &pc;
  
Bike* pb = new Bike;
Bike** ppb = &pb;

Vehicle** ppvc = ppc;  // Недопустимо в языке
*ppvc = new Bike;      // Присваивание объекту, являющемуся объектом
                       // типа Car, объекта типа Bike

// Создадим функцию, которая использовала бы подобную конвертацию
void do_the_job(Vehicle** ppv) {
  *ppv = new Vehicle;  // Через указатель на указатель на базовый класс
                       // создаётся новый объект базового класс Vehicle
}

do_the_job(ppb);       // Если бы преобразование работало, мы бы смогли
                       // вызвать эту функцию с ppb.
*ppb->bike_method();   // Ой-ой. А объект-то на самом деле не Bike