Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Виртуальные функции и полиморфические кластеры
В С++ позднее связывание охватывает ряд функций-членов (методов), называемых виртуальными функциями. Виртуальная функция объявляется в базовом или в производном классе и, затем, переопределяется в наследуемых классах. Совокупность классов (подклассов), в которых определяется и переопределяется виртуальная функция, называется полиморфическим кластером, ассоциированным с некоторой виртуальной функцией. В пределах полиморфического кластера сообщение связывается с конкретной виртуальной функцией-членом во время выполнения программы. В Smalltalk, чисто объектно-ориентированном языке, полиморфизм охватывает все методы в системе, поэтому вся система Smalltalk является полиморфическим кластером. Обычную функцию-член можно переопределить в наследуемых классах. Однако без атрибута virtual такая функция-член будет связана с сообщением на этапе компиляции. Атрибут virtual гарантирует позднее связывание в пределах полиморфического кластера. Чтобы добиться позднего связывания для объекта, его нужно объявить как указатель или ссылку на объект соответствующего класса. Для открытых производных классов указатели и ссылки на объекты этих классов совместимыми с указателями и ссылками на объекты базового класса (т.е. к объекту производного класса можно обращаться, как будто это объект базового класса). Выбранная функция-член зависит от класса, на объект которого указывается, но не от типа указателя. Часто возникает необходимость передачи сообщений объектам, принадлежащим разным классам в иерархии. Каждый класс в иерархии отвечает на сообщение по-своему. В этом случае требуется реализация механизма позднего связывания. В С++ этот механизм реализован для виртуальных функций (объявление с использованием ключевого слова virtual). С++ поддерживает virtual функции-члены, которые объявлены в основном классе и переопределены в порожденном классе. Иерархия классов, определенная общим наследованием, создает связанный набор типов пользователя, на которые можно ссылаться с помощью указателя базового класса. При обращении к виртуальной функции через этот указатель в С++ выбирается соответствующее функциональное определение во время выполнения. Объект, на который указывается, должен содержать в себе информацию о типе, поскольку различия между ними может быть сделано динамически. Это особенность типична для ООП кода. Каждый объект “знает” как на него должны воздействовать. Эта форма полиморфизма называется чистым полиморфизмом. Виртуальные функции позволяют управляемым классам определять различные версии функций базового класса. В С++ функции-члены класса с различным числом и типом параметров есть действительно различные функции, даже если они имеют одно и тоже имя. Виртуальные функции позволяют переопределять в управляемом классе функции, введенные в базовом классе, даже если число и тип аргументов то же самое. Для виртуальных функций нельзя переопределять тип функции. Если две функции с одинаковым именем будут иметь различные аргументы, С++ будет считать их различными и проигнорирует механизм виртуальных функций. Для определения виртуальной функции служит ключевое слово virtual. Виртуальная функция обязательно член класса. Например: struct B { virtual void vf1(); virtual void vf2(); virtual void vf3(); void f(); }; class D: public B { public: virtual void vf1(); // виртуальная функция void vf2(int); // не виртуальная функция // другой список аргументов char vf3(); // недопустимо - изменен тип void f(); }; void main() { D d; B *bp=& d; bp-> vf1(); // вызов D:: vf1 bp-> vf2(); // вызов B:: vf2 bp-> f(); // вызов B:: f } Рассмотрим механизм создания полиморфического кластера виртуальной функции. class Parent { protected: char *lastName; public: Parent(void) { lastName=new char[5]; strcpy(lastName, ”None”); } Parent(char *a) { lastName=new char [strlen(a)+1]; strcpy(lastName, a); } Parent(Parent & a) { lastName=new char[strlen(a.lastName)+1]; strcpy(lastName, a.lastName); } char *getLastName(void) { return lastName; } void setLastName(char *a) { lastName=new char[strlen(a)+1]; strcpy(lastName, a); } virtual void answerName(void) { cout < < “My last name is” < < lastName < < “ } ~Parent(void) { delete lastName; } }; class Child: public Parent { protected: char *firstName; public: Child(void) { firstName=new char[5]; strcpy(firstName, ”None”); } Child(char *a, char *a1): Parent(a){ firstName=new char[strlen(a1)+1]; strcpy(firstName, a1); } Child(Child & a): Parent(a) { setLastName(a.getLastName()); firstName=new char[strlen(a.firstName)+1]; strcpy(firstName, a.firstName); } char *getFirstName(void) { return firstName; } void setFirstName(char *a) { firstName=new char[strlen(a)+1]; strcpy(firstName, a); } ~Child(void) { delete firstName; } virtual void answerName(void) { Parent:: answerName(); cout < < “My first name is ” < < firstName < < “\n”; } }; class GrandChild: public Child { private: char *grandFatherName; public: GrandChild(char*a, char*a1, char*a2): Child(a, a1){ grandFatherName=new char[strlen(a2)+1]; strcpy(grandFatherName, a2); } ~GrandChild(void) { delete grandFatherName; } virtual void answerName(void) { Child:: answer(); cout < < “My grandfather name is “ < < grandFatherName < < “\n”; } }; void main(void) { Parent *family[3]; \ Parent *p=new Parent(“ Иванов ”); Child *c=new Child(“ Иванов ”, ”Сергей”); CrandChild *g=new GrandChild(“Иванов”, ”Андрей”, ”Владимир”); family[0]=p; family[1]=c; family[2]=g; for(int index=0; index< 3; index++) family[index]-> answerName(); } Результат работы программы My last name is Иванов My last name is Иванов My first name is Сергей My last name is Иванов My first name is Андрей My grandfather’s name is Владимир
Вопросы: Что, если в производных классах Child и GrandChild опустить ключевое слово virtual перед функцией-членом answerName? Программа будет работать отлично. Ключевое слово virtual необходимо в полиморфическом кластере только перед виртуальной функцией самого верхнего уровня. Для всех остальных переопределений этой функции ключевое слово virtual необязательно. Что, если убрать ключевое слово virtual перед всеми функциями-членами answerName? На выходе получим: My last name is Иванов My last name is Иванов My last name is Иванов Для реализации полиморфизма можно использовать ссылки на объекты вместо указателей. void main(void) { Parent p(“Иванов”); Child c(“Иванов”, ”Сергей”); CrandChild g(“Иванов”, ”Андрей”, ”Владимир”); Parent & f0=p, & f1=c; & f2=g; f0.answerName(); f1.answerName(); f2.answerName(); } Рассмотрим тонкости виртуальной функции class P { private: virtual void method1(void) {} void method2(void){} void method3(void){method1(); method2(); } // this-> method1, this-> method2 void method5(void){} public: void method6(void){method3(); } void method4(void){} void method7(void){method5(); } }; class C: public P { private: void method1(void) {} void method5(void) {} public: C(){} void method4(void) {} void method7(void) { method5(); } }; void main() { P pop; C me; pop.method6(); // P:: method1, P:: method2 me.method6(); // C:: method1, P:: method2 pop.method4(); // P:: method4 me.method4(); // C:: method4 pop.method7(); // P:: method5 me.method7(); // C:: method5 } Сообщение method6() посылается объекту me. Это сообщение унаследовано из класса P. Вызывается защищенный метод method3() и, затем, защищенные методы method1() и method2(). Так как method1() объявлен в базовом классе Р виртуальным, система на этапе выполнения программы связывает сообщение this-> method1() с method1() класса C. При перегрузке функции-члена виртуальный функции способны вызвать смешивание и беспорядок. class B { public: virtual foo(int); virtual foo(double); }; class D: public B { public: foo(int); }; void main() { D d; B b, *pb=& d; b.foo(9); // B:: foo(int); b.foo(9.5); // B:: foo(double) pb-> foo(9.5); // B:: foo(double); pb-> foo(9); // D:: foo(int) } Функция-член базового класса B:: foo(int) в порожденном переопределена. Функция-член базового класса B:: foo(double) в порожденном классе скрыта. Virtual могут быть только нестатические функции-члены. Функция порожденного класса автоматически становится виртуальной. Специальным ограничением является: деструкторы могут быть виртуальными, а конструкторы - нет. Рассмотрим пример преимущества использования виртуальных функций для упрощения кода: class shape { protected: double x, y; public: virtual double area() { return 0.; } }; class rectangle: public shape { double height, width; public: double area() { return (height*width); } }; class circle: public shape { double radius; public: double area() { return (PI*radius*radius); } }; Вычисление площади является локальной ответственностью порожденного класса. Код пользователя, который использует полиморфное вычисление площади, может выглядеть так: shape *p[N]; .......... for(i=0; i< N; i++) tot_area+=p[i]-> area(); Здесь главное преимущество состоит в том, что код пользователя не нуждается в изменении, даже если к системе добавляются новые формы. Изменения управляются локально и распространяются автоматически, благодаря полиморфному характеру кода пользователя. Популярное:
|
Последнее изменение этой страницы: 2016-07-14; Просмотров: 550; Нарушение авторского права страницы