Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АрхитектураБиологияГеографияДругоеИностранные языки
ИнформатикаИсторияКультураЛитератураМатематика
МедицинаМеханикаОбразованиеОхрана трудаПедагогика
ПолитикаПравоПрограммированиеПсихологияРелигия
СоциологияСпортСтроительствоФизикаФилософия
ФинансыХимияЭкологияЭкономикаЭлектроника

Множественное наследование

Наследование классов | Применение наследования | Использование объектов производного класса в качестве объектов базового класса | Dynamic_cast <тип *> (выражение). | Информация о типе во время выполнения (RTTI) (лучше почитать после обсуждения виртуальных функций). | Конструкторы, деструктор и перегрузка опрации присваивания в производном классе. | Виртуальные функции | Плата за полиморфизм |


Читайте также:
  1. Визуальное объектно-ориентирование программирование. Инкапсуляция, наследование, полиморфизм. Основные объекты и их свойства, методы и события
  2. Генитив множественное число
  3. Для классов, связанных наследованием, существует расширенная совместимость типов (совместимы по представлению в памяти).
  4. Замена множественного наследования наследованием от интерфейсов в других языках объектно-ориентированного программирования
  5. Имя существительное: склонение, множественное число. Артикль. Соединительный союз.
  6. Множественное наследование

 

Множественное наследование предполагает, что производный класс порождается из двух или более базовых классов. Концепция множественного наследования довольно противоречивая и дискуссионная тема. Потенциально, возможности ее велики. Множественное наследование позволяет построить более адекватную модель прикладной области и выразить средствами языка программирования сложную структуру непосредственно. С другой стороны, множественное наследование, часто, может создать проблемы в программах. Из-за того, что имя метода или поля может по-разному проявлять себя в разных базовых классах, а различные базовые классы могут быть сами порождены от общего предка (и много других проблем), то корректно написать большую программу с использованием сложной иерархии множественного наследования весьма и весьма трудно. Поэтому, применять множественное наследование нужно с большим вниманием и только тогда, когда это абсолютно необходимо.

 

Следующий формальный пример демонстрирует синтаксис множественного наследования:

 

Пример 15-3.

 

#include <iostream.h>

 

class A

{

int a;

public:

A(int i): a(i) { }

virtual void print() {cout << a << endl;}

int get_a() {return a;}

};

 

class B

{

int b;

public:

B(int j): b(j) { }

void print() {cout << b << endl;}

int get_b() {return b;}

};

 

class C: public A, public B

{

int c;

public:

C(int i, int j, int k): A(i), B(j), c(k) { }

void print() {A::print(); B::print();}

// используем print() с операцией разрешения обл. действия ::

void get_ab() {cout << get_a() << " " << get_b() << endl;}

// используем get_a() и get_b() без операции ::

};

 

int main()

{

C x(5, 8, 10);

A* ap = &x;

B* bp = &x;

 

ap -> print(); // используем C::print();

bp -> print(); // используем B::print();

// bp -> A::print(); // как будто x наследовано только из B,

// доступ A::print() - невозможен;

x.A::print(); // используем A::print();

x.get_ab();

 

return 0;

}

 

Класс C – наследник обоих классов A и B. Поэтому, объект класса C имеет данные-члены a, b, и c, или более точно, объект класса C строится “на вершине” (является надстройкой) копии объекта класса A с полем a, и копии объекта класса B с полем b, добавляя свое (дополнительное) поле c.

 

Проблема возникает, если как класс A, так и B - порождены от общего базового класса R:

 

class R

{

int r;

public:

// методы класса R

};

 

class A: public R

{

int a;

public:

// методы класса A

};

 

class B: public R

{

int b;

public:

// методы класса B

};

 

class C: public A, public B

{

int c;

public:

// методы класса C

};

 

В этом случае объект класса есть надстройка над объектом класса A и объекта класса B, но оба последних содержат объект класса R. В соответствии с “идеологией”, применяемой при одиночном наследовании, мы должны бы создать объект класса R, чтобы сконструировать объект класса A, затем создать, возможно, другой объект класса R для конструирования объекта класса B, и, наконец, сконструировать объект класса C как надстройку объектов класса A и B. Эти два объекта класса R не взаимодействуют друг с другом. Такое дублирование (объекта класса R) является верным, но во многих случаях не необходимым, а даже бессмысленным. Например, пусть, с одной стороны, нашему приложению действительно нужно использовать один и тот же объект для построения объекта класса A и объекта класса B. Но, с другой стороны, в этом случае объект класса C не может быть использован для представления объектов класса R, поскольку он содержит два объекта класса R. О такого рода проблемах при множественном наследовании говорят как о возникновении неоднозначностей.

 

C++ обеспечивает способ, позволяющий иметь только один объект класса R при построении объекта класса A и объекта класса B. Речь идет о, так называемом, виртуальном наследовании: необходимо объявить класс R как виртуальный базовый класс. (Это не совсем удачно, но мы используем слова “ абстрактный базовый класс” для классов, содержащих хотя бы одну чисто виртуальную ф-ию, и слова “ виртуальный базовый класс” – для классов, которые могут не содержать виртуальных функций вообще.)

 

Если A и B виртуально наследуются из класса R, то тогда объект любого класса, наследованного из обоих классов A и B, будет содержать только одну копию объекта класса R. Ответственность за конструирование этой общей копии лежит на производном классе. Теперь, если класс C порожден из A и B, то конструктор класса C должен создать, сначала, объект класса R, а затем сконструировать объект класса A и объект класса B, который должен будет содержать эту общую копию объекта класса R (не дублируя). Если пользоваться терминологией объектов и подобъектов, то в цепи наследований каждый невиртуальный базовый класс дает в результате отдельный (свой) подобъект, а каждый виртуальный базовый класс – единственный подобъект, независимо от того, сколько раз класс присутствует в графе (иерархии) наследования.

 

Пример 15-4.

 

class R

{

int r;

public:

R (int x = 0): r(x) { } // конструктор в R

// другие методы класса R

};

 

class A: public virtual R

{

int a;

public:

A (int x, int y): R(x), a(y) { } // конструктор в A

// другие методы класса A

};

 

class B: public virtual R

{

int b;

public:

B(int x, int z): R(x), b(z) { } // конструктор в B

// другие методы класса B

};

 

class C: public A, public B

{

int c;

public:

C(int x, int y, int z, int w): R(x), A(x, y), B(x, z), c(w) { }

// конструктор в C, который конструирует сначала объект класса R

// другие методы класса C

};

 

Могут быть, тем не менее, проблемы и с вызовами методов. Предположим, что класс R имеет функцию f(), класс A имеет функцию, называющуюся также f(), которая обращается к f() из R, делая что-нибудь для ее R-базового объекта, а сама также делает что-нибудь для своего собственного класса A, при этом и класс B тоже имеет функцию с именем f(), которая вызывает f() из R, делая что-то для R-объекта, а также и для своего класса B. Теперь, пусть некая функция f() опять определяется в классе C. Эта функция вызывает A::f() и B::f(), чтобы сделать нечто для ее базовых объектов классов A и B, соответственно, и, возможно, делает что-нибудь еще для самого класса C. И теперь, когда вызывается C::f(), то дважды вызывается R::f() для одного и того же объекта класса R. В большинстве случаев, происходящее -это не то, что мы хотели сделать. Вот идиома (пример использования идиомы: “ собаку съелв C++ – неразложимое выражение, смысл котор. не совпадает со значениями слов; в данном контексте – словесная формула), называемая идиомой неопределенности функций при множественном наследовании, которая решает эту проблему:

“В классе A используй функцию fA(), чтобы сделать работу, которую f() приучена делать для самого класса A, и позволь функции f() вызвать R::f() и A::fA(), чтобы завершить ее работу. Сделай то же самое в классе B.” См. Пример:

 

Пример 15-5, “Разделяй и властвуй”

 

class R

{

//...

public:

void f();

//...

};

 

class A: virtual public R

{

//...

protected:

void fA();

//...

public:

void f() {fA(); R::f();}

//...

};

 

class B: virtual public R

{

//...

protected:

void fB();

//...

public:

void f() {fB(); R::f();}

//...

};

 

class C: public A, public B

{

//...

protected:

fC(); // заботится только о классе C

//...

public:

void f()

{

R::f();

A::fA();

B::fB();

fC();

}

//...

};

 

Функция f() в классе R также может быть объявлена как виртуальная. Тогда посредством указателя или ссылки на класс R, при использовании для производного класса, можно обратиться к подходящей функции f() в соответствии с реальным типом объекта.

 

Иерархия наследования, использующая виртуальные базовые классы и идиому неопределенности функций при множественном наследовании, называется канонической формой наследования. Если при разработке программы допускается применение в будущем множественного наследования, то эта каноническая форма должна быть принята во внимание. В предыдущем примере класс C на текущей стадии разработки может быть еще не определен. Если ожидается, что подобный класс C будет в будущем, то функции A::f() и B::f() должны быть определены в соответствии с канонической формой наследования.

 

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

 

Пример 15-6.

 

#include <iostream.h>

#include "Vector.cpp"

// Шаблон класса Vector смотри в Лекции No 12 о конструкторах и деструкторах…

 

// абстрактный базовый класс стека (в виде шаблона класса)

template <class T>

class Stack

{

public:

virtual bool empty() = 0;

virtual bool full() = 0;

virtual Stack& push(T& new_member) = 0;

virtual Stack& pop() = 0;

virtual T& top() = 0;

};

 

// конкретный класс стека, использующий реализацию класса Vector

template <class T>

class VecStack: public Stack<T>, private Vector<T*>

{

public:

bool empty()

{ return get_size() == 0; }

 

bool full()

{ return false; }

 

Stack<T>& push(T& new_member)

{

add(&new_member);

return *this;

}

 

Stack<T>& pop()

{

if (!empty())

{ remove(); }

return *this;

}

 

T& top()

{ return *(*this)[get_size() - 1]; }

};

 

int main()

{

VecStack<int> s;

 

int one = 1, two = 2, three = 3;

 

s.push(one).push(two).push(three);

cout << s.top() << endl;

s.pop().pop();

cout << s.top() << endl;

s.pop();

if (!s.empty())

{ cout << "Error.\n"; }

else

{ cout << "Empty.\n"; }

 

return 0;

}

 

// Дальше… - можно пропустить.

 

Имитация динамического множественного наследования (необязательный пример)

 

Динамическое множественное наследование означает, что класс C может быть производным от класса A ИЛИ от класса B. Решение о том, как будет получен производный класс принимается во время выполнения программы. C++, в действительности, непосредственно не поддерживает такое динамическое множественное наследование. Однако, если как класс A, так и класс B порождены из общего базового класса R, то существует способ имитировать поведение класса, динамически наследуемого из A или B.

 

Хитрость состоит в том, чтобы добавить в класс C член, который является указателем на класс R. Этот указатель может быть связан либо с объектом класса A, либо с объектом класса B во время выполнения кода. Когда к некоторой виртуальной функции из класса R выполяется обращение через этот указатель, то объект класса C может вести себя так, как будто он наследован от класса A или класса B (напомним, что поведение объектов класса определяется его методами), сами же объекты классов A или B могут быть созданы “по ходу дела”, т.е. динамически, в методе класса C.

 

Пример 15-7

 

#include <iostream.h>

 

class R

{

protected:

int r;

public:

R(int x = 0): r(x) { }

virtual void print() {cout << "class R print" << endl;}

};

 

class A: public R

{

protected:

int a;

public:

A(int x = 0, int y = 0): R(x), a(y) { }

void print() {cout << "class A print" << endl;}

};

 

class B: public R

{

protected:

int b;

public:

B(int x = 0, int z = 0): R(x), b(z) { }

void print() {cout << "class B print" << endl;}

};

 

enum from {A_type, B_type};

 

class C

{

int c;

R * rptr;

public:

C(): rptr(NULL) { } // конструктор по умолчанию

C& C_ctor(int x, int w, int z, from t) // метод - 'именованный конструктор'

{

if (t == A_type)

{

A * aptr = new A(x, w);

rptr = aptr;

}

if (t == B_type)

{

B * bptr = new B(x, w);

rptr = bptr;

}

c = z;

return *this;

}

void print() {rptr -> print();} //вызов через указатель R * rptr

~C() {delete rptr;}

};

 

int main()

{

C c;

int n;

 

cout << "Enter an integer.\n";

cin >> n;

 

if (n % 2)

{

// c ведет себя как производный от A

c.C_ctor(1, 2, 3, A_type);

c.print();

}

else

{

// c ведет себя как производный от B

c.C_ctor(1, 2, 3, B_type);

c.print();

}

return 0;

}

 

 


Дата добавления: 2015-11-16; просмотров: 55 | Нарушение авторских прав


<== предыдущая страница | следующая страница ==>
Абстрактные базовые классы и чисто виртуальные функции| Виртуальные функции.

mybiblioteka.su - 2015-2024 год. (0.035 сек.)