Разберемся теперь с операцией присваивания. Так как в базовом классе операция operator= не определена, компилятор создает ее по умолчанию. Как мы уже знаем (см. п. п. 12.8/10 в [1]), такая операция для базового класса имеет прототип:
TCurrency& operator=(const TCurrency &);
Для класса-наследника создается аналогичная функция:
Roubles& operator=(const Roubles &);
Наличие этих функций позволяет нам выполнять следующие присваивания:
TCurrency cl(10)( с2(11); // переменные базового класса
Roubles rl(20), г2(21); // переменные производного класса
cl = с2; // операция базового класса
rl = г2; // операция производного класса
cl = г2; // базовый = производный; операция базового класса
В последнем случае работает принцип подстановки: справа от присваивания задан объект производного класса, который подставляется на место объекта базового класса в операции присваивания.
Однако мы не можем выполнить присваивание
rl = cl; // производный = базовый
Неявное преобразование типа не работает, никакие явные преобразования не помогают. Такое присваивание не проходит и для указателей (ссылок), например:
TCurrency cl(10): // переменная базового класса
Roubles *рг: // указатель производного класса
pr = &cl; // ошибка трансляции
Компилятор сообщает, что он не может преобразовать адрес объекта базового класса в адрес производного. Чтобы это присваивание работало, требуется определить операцию присваивания с прототипом
Roubles& operator=(const TCurrency &);
Стандарт не запрещает писать такие операции присваивания. Более того, в любом классе можно неоднократно перегрузить операцию присваивания, в одной из которых, например, ни аргумент, ни результат не являются определяемым классом (см. п. п. 12.8/9 в [1]). Единственным ограничением является видимость определения нужных классов в точке определения операции присваивания.
В данном случае мы фактически должны написать функцию, аналогичную функции преобразования типа из базового типа в производный. Такое приведение в [11] названо понижающим.
ВНИМАНИЕ
В С++ понижающее приведение можно выполнить с помощью оператора преобразования dynamiccast (см. п. п. 10.3/1 в [1]), которое работает для полиморфных классов. Полиморфный класс — это класс с виртуальными функциями.
Именно из-за отсутствия такой операции не работал оператор f = d+d;
Рассмотрим, что должна делать эта функция и какие при этом возникают проблемы. В базовом классе такую функцию определить невозможно, так как базовый класс «понятия не имеет» о своих наследниках. Попытка ее создания немедленно приводит к ошибке трансляции класса TCurrency, так как Roubles не является определенным к этому моменту типом. Не спасает и предварительное объявление класса:
class Roubles:
Так как в этом случае возникает другая ошибка — отсутствие определения класса.
Следовательно, нужно определить требуемую функцию в производном классе. Но и в этом случае некоторые проблемы остаются. Во-первых, мы тогда должны переопределять эту функцию в каждом классе-наследнике (так как присваивание не наследуется, как и конструкторы с деструкторами). Во-вторых, что важнее, наша функция должна присвоить поля базового класса соответствующим полям производного класса, однако непосредственно она не может этого сделать, так как поля базового класса недоступны — они приватные.
«А давайте..!» — нет, открывать закрытые поля мы не будем. Не для того городился огород с инкапсуляцией, чтобы вот так просто его разрушить. Даже перевод полей в защищенные открывает «ящик Пандоры» — любой наследник получает к ним доступ. Вы только подумайте: достаточно унаследовать от TCurrency — и можно делать с суммами все, что заблагорассудится! Это — тот самый путь, который ведет в Ад.
Решение этой проблемы можно найти в стандарте (см. п. п. 12/1 в [1]): Roubles& Roubles::operator=(const TCurrency &t)
{ this->TCurrency::operator=(t); // вызов родительской операции
return "this;
Собственно, самая главная «фишка» — это вызов родительской операции в дочерней, причем в функциональной форме. Это работает, несмотря на то, что в родительском классе операция присваивания не была явно определена.
При наследовании денег не просматривается еще одна проблема, связанная с принципом подстановки. Чтобы понять, в чем дело, обратимся к классам точек и рассмотрим простой пример с двухмерными и трехмерными точками.
Point3D b(l,2,3);
Point2D а = b; // подстановка в конструкторе копирования - срезка
а = Ь; // подстановка в присваивании - срезка
Мобильные приложения необходимые для работы вне офиса вы можете найти тут http://softsprint.net/mobile/.
Работает принцип подстановки, однако нас поджидает неприятность: так как базовый класс ничего не знает о своих наследниках, то в переменную а копируется только двухмерная (Poi nt2D) часть трехмерной точки. Этот эффект называется срезкой [2], или расщеплением [11]; он частенько приводит к ошибкам. Например, при передаче параметра по значению, как мы знаем, работает конструктор копирования, поэтому в таких случаях тоже может произойти срезка. При передаче параметра по ссылке (или по указателю) ничего подобного не происходит. При передаче параметров в блок обработки исключения тоже может произойти срезка, поэтому параметр в секцию-ловушку лучше передавать по ссылке. |