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

Хорошо я читалаэта запись в FQA имея дело с проблемой преобразованияDerived** кBase** и почему это запрещено, и я понял, что проблема в том, что вы можете назначитьBase* то, что не являетсяDerived*поэтому мы запрещаем это.

Все идет нормально.

Но если мы применяем этот принцип в глубине, почему мы не запрещаем такой пример?

void nasty_function(Base *b)
{
  *b = Base(3); // Ouch!
}

int main(int argc, char **argv)
{
  Derived *d = new Derived;
  nasty_function(d); // Ooops, now *d points to a Base. What would happen now?
}

я согласна с тем чтоnasty_function делает что-то идиотское, поэтому мы могли бы сказать, что допускать такое преобразование - это хорошо, потому что мы разрешаем интересные проекты, но мы можем сказать, что и для двойной косвенности:Base **, но вы не должны связывать что-либо с его почтением, потому что вы действительно не знаете, где этоBase ** приходит, как иBase *.

Итак, вопрос: что особенного в этом дополнительном уровне косвенности? Возможно, дело в том, что, имея всего один уровень косвенности, мы могли бы играть с виртуальнымoperator= чтобы избежать этого, хотя такой же механизм недоступен для простых указателей?

 J-16 SDiZ29 июн. 2012 г., 17:30
хм, разве FQA / FAQ не ответили точно на то, что вы спрашиваете?
 akappa29 июн. 2012 г., 17:31
не совсем, потому что они не указывают, что вы могли бы сделать тот же трюк с простыми указателями

Ответы на вопрос(6)

Ну, код, который вы дали, имеет смысл. Действительно, оператор присваивания не может перезаписывать данные, относящиеся к производным, а только к базе. Виртуальные функции все еще из производных, а не из базы.

*b = Base(3); // Ouch!

Здесь объект на*b на самом деле этоBэто базовый подобъект*d, Изменяется только этот базовый подобъект, остальная часть производного объекта не изменяется иd все еще указывает на тот же объект производного типа.

Возможно, вы не захотите позволять изменять базу, но с точки зрения правильности системы типов.Derived is a Base.

Это неверно для случая с недопустимым указателем.Derived* конвертируется вBase* но это не тот же тип. Это нарушает систему типов.

Разрешение конверсии, о которой вы спрашиваете, ничем не отличается от этого:

Derived* d;
Base b;
d = &b;
d->x;

Читая хорошие ответы на мой вопрос, я думаю, что я понял суть проблемы, которая проистекает из первых принципов ОО и не имеет ничего общего с подобъектами и перегрузкой операторов.

Дело в том, что вы можете использоватьDerived всякий раз, когдаBase требуется (принцип замещения), но выcannot использоватьDerived* всякий раз, когдаBase* необходим из-за возможности присвоения ему указателей экземпляров производных классов.

Возьмите функцию с этим прототипом:

void f(Base **b)

f может сделать кучу вещей сbразыменовывая его среди прочего:

void f(Base **b)
{
  Base *pb = *b;
  ...
}

Если мы перешли кf Derived**, это означает, что мы используемDerived* какBase*, что неверно, так как мы можем назначитьOtherDerived* вBase* но не дляDerived*.

С другой стороны, возьмите эту функцию:

void f(Base *b)

Еслиf разыменовываетbтогда мы будем использоватьDerived вместоBase, что вполне нормально (при условии правильной реализации иерархии классов):

void f(Base *b)
{
  Base pb = *b; // *b is a Derived? No problem!
}

Скажем по-другому: принципы замещения (используйте производный класс вместо базового) работают наinstances, не наpointersпотому что «концепция» указателя наA is & quot; указывает на экземпляр того класса, который наследуетA& quot; и набор классов, которые наследуютсяBase строго содержит набор классов, который наследуетDerived.

Решение Вопроса
nasty_function(d); // Ooops, now *d points to a Base. What would happen now?

Нет, это не так. Это указывает наDerived, Функция просто изменилаBase подобъект в существующемDerived объект. Рассматривать:

#include <cassert>

struct Base {
    Base(int x) : x(x) {}
    int x;
};
struct Derived : Base {
     Derived(int x, int y) : Base(x), y(y) {}
     int y;
};

int main(int argc, char **argv)
{
  Derived d(1,2); // seriously, WTF is it with people and new?
                  // You don't need new to use pointers
                  // Stop it already
  assert(d.x == 1);
  assert(d.y == 2);
  nasty_function(&d);
  assert(d.x == 3);
  assert(d.y == 2);
}

d волшебным образом не становитсяBase, Является ли? Это все ещеDerived, ноBase часть этого изменилась.

В картинках :)

Это то, чтоBase а такжеDerived объекты выглядят так:

Layouts

Когда у нас есть два уровня косвенности, это не работает, потому что назначаемые объекты являются указателями:

Assigning pointers - type mismatch

Обратите внимание, как ни один изBase или жеDerived рассматриваемые объекты пытаются изменить: только средний указатель.

Но, когда у вас есть только один уровень косвенности, код изменяет сам объект таким образом, который позволяет объект (он может запретить это, делая закрытый, скрывая или удаляя оператор присваивания изBase):

Assigning with only one level of indirection

Обратите внимание, как здесь не меняются указатели. Это так же, как любая другая операция, которая изменяет часть объекта, какd.y = 42;.

 akappa29 июн. 2012 г., 18:01
Но я сделал это, потому что мы говорили о преобразовании указателей, поэтомуDerived * переменные делают это явным. Я мог бы сделатьDerived a; Derived *b = &a, но я думаю, что это смешно, потому что здесьnew бесполезен и его следует избегать, как ад & quot; - Я имею в виду, даже если мы программируем компьютеры, нам следует сохранять некоторую гибкость для себя.
 29 июн. 2012 г., 18:01
@akappa Доказательство концепции с использованием современных идиом C ++ занимает меньше времени и все еще верно, в отличие от доказательства концепции с уродливыми идеями до 98 года.
 akappa29 июн. 2012 г., 17:50
Да, я думаю, что я должен сохранить этот «подобъект» вещь в моей ментальной модели. Кстати, ваш комментарий в разделе "новый и указатель" является грубым, поскольку это код, предназначенный для демонстрации концепции.
 29 июн. 2012 г., 17:58
Нет, я думаю, что это правильное количество хешей. Слишком много программистов на Java пишутnew в каждой строке C ++, и это просто глупо.

*b = Base(3) звонкиBase::operator=(const Base&), который на самом деле присутствует вDerived как функции-члены (в т.ч. операторы) наследуются.

Что будет потом (звонитDerived::operator=(const Base&)) иногда называется& Quot; нарезка & Quot;и да, это плохо (обычно). Это печальное следствие явной вездесущности «становиться подобным». оператор (=) в C ++.

(Обратите внимание, что оператор «стать похожим» не существует в большинстве ОО-языков, таких как Java, C # или Python;= в контексте объекта имеется в виду присвоение ссылки, аналогичное присвоению указателя в C ++;).

Подводя итог:

СлепкиDerived** - & GT;Base** запрещены, потому что они могут вызватьtype errorпотому что тогда вы можете получить указатель типаDerived* указывая на объект типаBase.

Упомянутая вами проблема не является ошибкой типа; это другой тип ошибки:mis-use of the interface изderived объект, коренящийся из-за печального факта, что он унаследовал «становиться похожим» оператор своего родительского класса.

(Да, я называю op = в контекстах объектов «преднамеренно», поскольку считаю, что «назначение» не является хорошим именем, чтобы показать, что здесь происходит.)

 akappa29 июн. 2012 г., 17:51
Да, внешне они выглядят как одна и та же проблема, но это две совершенно разные вещи. Спасибо!

Нет,nasty_function() не так противно, как кажется. Как указательb указывает на то, чтоis-a Baseсовершенно законно назначитьBase-значение этому.

Берегите себя: ваши & quot; Упс & quot; комментарий не верный:d все еще указывает на то же самоеDerived как и до звонка! ТолькоBase часть его была переназначена (по значению!). Если это получит весь вашDerived из-за непоследовательности, вам нужно изменить дизайн, сделавBase::operator=() виртуальная. Затем вnasty_function()на самом делеDerived будет вызван оператор присваивания (если он определен).

Итак, я думаю, ваш пример не имеет ничего общего со случаем указателя на указатель.

Ваш ответ на вопрос