Динамический полиморфизм

Автор работы: Пользователь скрыл имя, 07 Декабря 2013 в 07:54, реферат

Краткое описание

В информатике, полиморфизм — свойство языка программирования, позволяющее единообразно обрабатывать данные разных типов. Существует несколько принципиально различных видов полиморфизма, два из которых были описаны Кристофером Стрэчи в 1967 г.
Если функция описывает разные реализации (возможно, с различным поведением) для ограниченного набора явно заданных типов и их комбинаций, это называется ситуативным полиморфизмом (ad hoc polimorphism). Ситуативный полиморфизм поддерживается во многих языках посредством перегрузки функций и методов.

Прикрепленные файлы: 1 файл

Omarov.docx

— 35.69 Кб (Скачать документ)

Динамический  полиморфизм

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

Если функция описывает  разные реализации (возможно, с различным  поведением) для ограниченного набора явно заданных типов и их комбинаций, это называется ситуативным полиморфизмом (ad hoc polimorphism). Ситуативный полиморфизм поддерживается во многих языках посредством перегрузки функций и методов.

Если же код написан отвлеченно от конкретного типа данных и потому может свободно использоваться с любыми новыми типами, имеет место параметрический полиморфизм. Джон С.Рейнольдс, и независимо от него Жан-Ив Жирар формально описали эту нотацию как развитие лямбда-исчисления (названную полиморфным лямбда-исчислением или Системой F). Параметрический полиморфизм широко используется в статически типизируемых функциональных языках программирования. В объектно-ориентированном сообществе программирование с использованием параметрического полиморфизма называется обобщённым программированием.

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

Некоторые языки совмещают  различные формы полиморфизма, порой  сложным образом, что формирует  самобытную идеологию в них и  влияет на применяемые методологии  декомпозиции задач. Например, в Smalltalk любой класс способен принять сообщения любого типа, и либо обработать его самостоятельно (в том числе посредством интроспекции), либо ретранслировать другому классу — таким образом, несмотря на широкое использование перегрузки функций, формально любая операция является неограниченно полиморфной и может применяться к данным любого типа.

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

Виды полиморфизма[править | править исходный текст]

Параметрический полиморфизм[править | править исходный текст]

См. также: en:Parametric polymorphism

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

Параметрический полиморфизм  повсеместно используется в функциональном программировании, где он обычно обозначается просто как «полиморфизм». Следующий пример демонстрирует параметризованный тип «список» и две опредёленные на нём параметрически полиморфные функции:

data List a = Nil | Cons a (List a)

 

length :: List a -> Integer

length Nil = 0

length (Cons x xs) = 1 + length xs

 

map :: (a -> b) -> List a -> List b

map f Nil = Nil

map f (Cons x xs) = Cons (f x) (map f xs)

Параметрический полиморфизм  связывается с подтипизацией понятиями связанной квантификации и ковариантности/контравариантности (или полярности) конструкторов типов.

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

Параметрический полиморфизм  также доступен в некоторых императивных (в частности, объектно-ориентированных) языках программирования, где для его обозначения обычно используется термин «обобщённое программирование»:

struct segment { int start; int end; };

 

int seg_cmpr( struct segment *a, struct segment *b )

{ return abs( a->end - a->start ) - abs( b->end - b->start ); }

 

int str_cmpr( char **a, char **b )

{ return strcmp( *a, *b ); }

 

struct segment segs[] = { {2,5}, {4,3}, {9,3}, {6,8} };

char* strs[] = { "three", "one", "two", "five", "four" };

 

main()

{

    qsort( strs, sizeof(strs)/sizeof(char*), sizeof(char*),

                 (int (*)(void*,void*))str_cmpr );

 

    qsort( segs, sizeof(segs)/sizeof(struct segment), sizeof(struct segment),

                 (int (*)(void*,void*))seg_cmpr );

    ...

}

 

class List<T> {

    class Node<T> {

        T elem;

        Node<T> next;

    }

    Node<T> head;

    int length() { ... }

}

 

List map( Func<A,B> f, List<A> xs ) {

    ...

}

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

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

Ситуативный (ad hoc) полиморфизм

Кристофер Стрэчи избрал термин «ситуативный полиморфизм» для описания полиморфных функций, вызываемых для аргументов разных типов, но реализующих различное поведение в зависимости от типа аргумента (называемых также перегруженными функциями иперегруженными операторами). Термин «ad hoc» (лат. спонтанный, сделанный под текущую задачу) в этом смысле не несёт уничижительного подтекста — он означает лишь, что этот вид полиморфизма не является фундаментальным свойством системы типов языка. В следующем примере функции Add выглядят как реализующие один и тот же функционал над разными типами, но компилятор определяет их как две совершенно разные функции.

program Adhoc;

 

function Add( x, y : Integer ) : Integer;

begin

    Add := x + y

end;

 

function Add( s, t : String ) : String;

begin

    Add := Concat( s, t )

end;

 

begin

    Writeln(Add(1, 2));

    Writeln(Add('Hello, ', 'World!'));

end.

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

Ситуативный полиморфизм  иногда используется совместно с  параметрическим. В языке Haskell стремление предоставить одновременно полиморфизм и перегрузку привело к необходимости введения принципиально нового понятия — классов типов (которые, вопреки распространённому заблуждению, не являются подмножеством объектно-ориентированной парадигмы программирования). С одной стороны, это позволяет существенно повысить выразительность программ, с другой чрезмерное их использование может приводить к сбоям механизма выведения типов, что вынуждает программистов отказываться от его использования, явно декларируя типы функций.

Полиморфизм подтипов

Некоторые языки представляют идею подтипизации для ограничения спектра типов, применимых в определённом частном случае параметрического полиморфизма. В этих языках полиморфизм подтипов (обычно называемый также динамическим полиморфизмом) позволяет функции, определённой на типе T, также корректно исполняться для аргументов, принадлежащих типу S, являющемуся подтипом T (в соответствии с принципом подстановки Барбары Лисков). Такое отношение типов обычно записывается как S <: T. При этом тип T называетсянадтипом (или супертипом) для S, что обозначается как T :> S.

Например, если имеются типы Number, Rational и Integer, связанные отношениями Number :> Rational и Number :> Integer, то функция, определённая на типе Number, также сможет принять на вход аргументы типов Integer или Rational, и её поведение будет идентичным. Действительный тип объекта может быть скрыт как «чёрный ящик», и предоставляться лишь по запросу идентификации объекта. На самом деле, если тип Number является абстрактным, то конкретного объекта этого типа даже не может существовать (см. абстрактный тип данных,абстрактный класс). Данная иерархия типов известна — особенно в контексте языка Scheme — как числовая башня, и обычно содержит большее количество типов.

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

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

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

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

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

abstract class Animal {

    abstract String talk();

}

 

class Cat extends Animal {

    String talk() { return "Meow!"; }

}

 

class Dog extends Animal {

    String talk() { return "Woof!"; }

}

 

 

public class MyClass {

 

    public static void write(Animal a) {

        System.out.println(a.talk());

    }

 

    public static void main(String args[]) {

        write(new Cat());

        write(new Dog());

    }

}

 

 

 

 

 

Виртуальный деструктор в C++

В языке программирования C++ деструктор полиморфного базового класса должен объявляться виртуальным. Только так обеспечивается корректное разрушение объекта производного класса через  указатель на соответствующий базовый  класс.

Рассмотрим следующий  пример.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

#include <iostream> 

 

using namespace std; 

 

// Вспомогательный класс

class Object 

{  

public:    

Object() { cout << "Object::ctor()" << endl; }   

~Object() { cout << "Object::dtor()" << endl; }

}; 

 

// Базовый класс

class Base

{  

public:    

Base() { cout << "Base::ctor()" << endl; }    

virtual ~Base() { cout << "Base::dtor()" << endl; }     

virtual void print() = 0;

}; 

 

// Производный класс

class Derived: public Base

{  

public:    

Derived() { cout << "Derived::ctor()" << endl; }   

~Derived() { cout << "Derived::dtor()" << endl; }        

void print() {}       

Object  obj;

}; 

 

int main ()

{    

Base * p = new Derived;    

delete p;    

return 0;

}


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

Вывод программы с использованием виртуального деструктора в базовом  классе будет следующим:

1

2

3

4

5

6

Base::ctor()

Object::ctor()

Derived::ctor()

Derived::dtor()

Object::dtor()

Base::dtor()


Уничтожение объекта производного класса через указатель на базовый  класс с невиртуальным деструктором дает неопределенный результат. На практике это выражается в том, что будет разрушена только часть объекта, соответствующая базовому классу. Если в коде выше убрать ключевое слово virtual перед деструктором базового класса, то вывод программы будет уже иным. Обратите внимание, что член данных obj класса Derived также не разрушается.

1

2

3

4

Base::ctor()

Object::ctor()

Derived::ctor()

Base::dtor()

 

Чисто виртуальные  функции и абстрактные типы


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

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

Когда виртуальная функция  не переопределена в производном  классе, то при вызове ее в объекте  производного класса вызывается версия из базового класса. Однако во многих случаях  невозможно ввести содержательное определение виртуальной функции в базовом классе. Например, при объявлении базового класса figure в предыдущем примере определение функции show_area() не несет никакого содержания. Она не вычисляет и не выводит на экран площадь объекта какого- либо типа. Имеется два способа действий в подобной ситуации. Первый, подобно предыдущему примеру, заключается в выводе какого-нибудь предупреждающего сообщения. Хотя такой подход полезен в некоторых ситуациях, он не является универсальным. Могут быть такие виртуальные функции, которые обязательно должны быть определены в производных классах, без чего эти производные классы не будут иметь никакого значения. Например, класс triangle бесполезен, если не определена функция show_агеа(). В таких случаях необходим метод, гарантирующий, что производные классы действительно определят все необходимые функции. Язык С++ предлагает в качестве решения этой проблемы чисто виртуальные функции.

Информация о работе Динамический полиморфизм