Стандартные преобразования типов при наследовании

Между порожденным и базовым классами существует связь, поэтому возможны некоторые преобразования.

Объект, порожденный всегда неявно преобразуется к объекту базового класса. Ссылка также преобразуется неявно.Указатель на произвольный класс преобразуется неявно в указатель на базовый класс.Указатель на член базового класса преобразуется неявно в указатель на член производного класса.

Указатели на объект базового и произвольного класса. Применительно к базовому и произвольному классу можно сказать, что преобразуя указатель на объект произвольного класса в указатель на объект базового класса, мы получаем доступ к вложенному объекту базового класса. Преобразуя тип указателя, компилятор должен учитывать размер объектов базового класса в произвольный. Так он и делает, но объект может оказаться не равным значению указателя на объект произвольного класса. Ввиду "особости" преобразование может быть выполнено неявно.

Побочный эффект состоит в том, что компилятор "забывает" об объекте произвольного класса и вместо переопределения функций, вызывает функции базового класса.

Пример:

class A

{ public:

void f1();

void f2();

}

A *pa;

BB *pb;

B x; // x-объект класса B

pa=&x; // неявно преобраз.

pa->f1(); // вызв. функция базового класса

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

pb=(B*) pa;

pb->f1(); // B::f1();

pb->f2();

Аппарат наследования классов предусматривает возможности преобразования типов между суперклассом и подклассом. Преобразование типов в каком-то смысле является формальным. Сам объект при таком преобразовании не изменяется, преобразование относится только к типу ссылки на объект .

Рассмотрим это на примере.

Пример

class A { int x; . . .} class B extends A { int y; . . .} B b = new B();A a = b; // здесь происходит формальное преобразование типа: B => A Различаются два вида преобразований типов — upcasting и downcasting . Повышающее преобразование (upcasting) — это преобразование от типа порожденного класса (от подкласса) к базовому (суперклассу). Такое преобразование допустимо всегда. На него нет никаких ограничений и для его проведения не требуется применять никаких дополнительных синтаксических конструкций (см. предыдущий пример). Это связано с тем, что объект подкласса всегда в себе содержит как свою часть объект суперкласса.

Понижающее преобразование (downcasting) — это преобразование от суперкласса к подклассу.

 

 


Инициализация объекта порожденного класса. Конструктор копии. Операция присваивания.

Инициализация объекта производного класса состоит из последовательного вызова конструктора базового класса и конструктора производного класса. Производный класс удобнее рассматривать как объект, состоящий из нескольких подобъектов: подобъекта базового класса, который инициализируется конструктором базового класса, и подобъекта производного класса, который инициализируется конструктором производного класса. Конструктор производного класса должен не только 'инициализировать члены данных производного класса, но и передать нужные параметры конструктору базового класса.

Конструктор копирования

Конструктор копирования-это специальный вид конструктора получающий в качестве единственного параметра ссылку на объект того же класса

T::T(const T&){…/*тело конструктора*/}

Этот конструктор вызывается в тех случаях, когда новый объект создается путем копирования существующего

-при описании нового объекта с инициализацией др объектом

-при передачи объекта в функцию по значению

-при возврате объекта из функции

Если программист не указал ни одного конструктора копирования, то компилятор создает его автоматически. такой конструктор выполняет поэлементное копирование полей.

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

date date2 = date1;

Однако имеются случаи, в которых создание объекта без вызова конструктора осуществляется неявно:

- формальный параметр - объект, передаваемый по значению, создается в стеке в момент вызова функции и инициализируется копией фактического параметра;

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

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

- dat2 в приведенном определении;

- создаваемого в стеке формального параметра;

- временного объекта, сохраняющего значение, возвращаемое функцией.

Вместо этого в них копируется содержимое объекта-источника:

- dat1 в приведенном примере;

- фактического параметра;

- объекта - результата в операторе return.

При наличии в объекте указателей на динамические переменные и массивы или идентификаторов связанных ресурсов, такое копирование требует дублирования этих переменных или ресурсов в объекте-приемнике, как это было сделано выше в операции присваивания. С этой целью вводится конструктор копирования, который автоматически вызывается во всех перечисленных случаях. Он имеет единственный параметр - ссылку на объект-источник:

class string

{

char *Str;

int size;

public:

string(string&); // Конструктор копирования

};

string::string(string& right) // Создает копии динамических

{ // переменных и ресурсов

s = new char[right->size];

strcpy(Str,right->Str);

}

Конструктор копирования обязателен, если в программе используются функции-элементы и переопределенные операции, которые получают формальные параметры и возвращают в качестве результата такой объект не по ссылке, а по значению.

Операции присваивания

При отсутствии переопределения операции присваивания производится побайтное копирование объектов. Такая интерпретация операции присваивания некорректна, если объект имеет указатели на динамические переменные или массивы, идентификаторы связанных ресурсов и т.д. При копировании таких объектов необходимо сначала уничтожить связанные динамические переменные и ресурсы левого операнда, а затем заново резервировать, но уже с параметрами, необходимыми для интерпретации операции присваивания.

Присваивание – это тоже операция, она является частью выражения. Значение правого операнда присваивается левому операнду.

x = 2; // переменной x присвоить значение 2cond = x < 2; // переменной cond присвоить значение true, если x меньше 2, // в противном случае присвоить значение false3 = 5; // ошибка, число 3 неспособно изменять свое значение

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

int x = 0;x = 3;x = 4;x = x + 1;

вначале объявляется переменная x с начальным значением 0. После этого значение x изменяется на 3, 4 и затем 5. Опять-таки, обратим внимание на последнюю строчку. При вычислении операции присваивания сначала вычисляется правый операнд, а затем левый. Когда вычисляется выражение x + 1, значение переменной x равно 4. Поэтому значение выражения x + 1 равно 5. После вычисления операции присваивания (или, проще говоря, после присваивания) значение переменной x становится равным 5.

У операции присваивания тоже есть результат. Он равен значению левого операнда. Таким образом, операция присваивания может участвовать в более сложном выражении:

z = (x = y + 3);

В приведенном примере переменным x и z присваивается значение y + 3.

Очень часто в программе приходится значение переменной увеличивать или уменьшать на единицу. Для того чтобы сделать эти действия наиболее эффективными и удобными для использования, применяются предусмотренные в Си++ специальные знаки операций: ++ (увеличить на единицу) и -- (уменьшить на единицу). Существует две формы этих операций: префиксная и постфиксная. Рассмотрим их на примерах.

int x = 0;++x;

Значение x увеличивается на единицу и становится равным 1.

--x;

Значение x уменьшается на единицу и становится равным 0.

int y = ++x;

Значение x опять увеличивается на единицу. Результат операции ++ – новое значение x, т.е. переменной y присваивается значение 1.

int z = x++;

Здесь используется постфиксная запись операции увеличения на единицу. Значение переменной x до выполнения операции равно 1. Сама операция та же – значение x увеличивается на единицу и становится равным 2. Однако результат постфиксной операции – это значение аргумента до увеличения. Таким образом, переменной z присваивается значение 1. Аналогично, результатом постфиксной операции уменьшения на единицу является начальное значение операнда, а префиксной – его конечное значение.

Подобными мотивами оптимизации и сокращения записи руководствовались создатели языка Си (а затем и Си++), когда вводили новые знаки операций типа "выполнить операцию и присвоить". Довольно часто одна и та же переменная используется в левой и правой части операции присваивания, например:

x = x + 5;y = y * 3;z = z – (x + y);

В Си++ эти выражения можно записать короче:

x += 5;y *= 3;z -= x + y;

Т.е. запись oper= означает, что левый операнд вначале используется как левый операнд операции oper, а затем как левый операнд операции присваивания результата операции oper. Кроме краткости выражения, такая запись облегчает оптимизацию программы компилятором.

 


Множественное наследование. Двусмысленности при множественном наследовании. Виртуальные базовые классы. Инициализация виртуального базового класса. Порядок вызовов конструкторов и деструкторов в случае нескольких базовых классов