[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
COM — всего лишь одно из мест где испрользуется полиморфизм — что бы у человека не возникло ощущения, что это только для COM, это не только — но описано либо в Страуструпе, либо разжовывается в COM в качестве прилюдии...
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Аноним, Вы писали:
... осваиваю виртуальные функции и динамичский полиморфизм, но не могу понять зачем он нужен и как его реализовать этот динамический полиморфизм.
...
Думаю наглядный пример помог бы мне разобраться, но здесь на форуме я ничего понятного для меня я не нашел.
IMHO: Цель максимум всего дизайна кода и использование обеих форм (динамической и статической) полиморфизма в частности сводиться к тому, чтобы новые возможности появлялись через
добавление нового, а
не изменение уже написанного кода.
Судя по словам, мой совет пока подождать с COM-ом. То, зачем полиморфизм нужен, становиться понятно из того, что он призван заменить.
Всегда можно написать код, проходящий через "спагетти" if-then-else/switch-case и вызывающий различные функции. Называйте это "полиморфизм на уровне ума разработчика", так как компилятор не имеет возможности реализовывать этот полиморфизм самостоятельно. Корень всех проблем в этом подходе — ручной труд (низкая скорость и ошибки).
В каждое новое место в программе, где нужно изменить поведение программы исходя из некоторой информации, вываливается новая порция "спагетии" ручного приготовления.
"Полиморфизм на уровне компилятора" (=настоящий полиморфизм) позволяет избавить разработчика от написания, чтения и отладки if-then-else/switch-case "спагетти". Кроме того, компилятор реализует это эффективно (чтобы разработчикам не было повода изобретать велосипеды).
Ниже приводится не самый полезный пример, но хотел оставить полный ответ (в книгах и жизни примеры практичнее зато могут запутать с непривычки).
#include <iostream>
using namespace std;
class unary_operation { // Базовый класс. В контексте полиморфизма - интерфейс.
// Перечисление в нём виртуальных функций даёт компилятору достаточно информации,
// чтобы генерировать _единообразный_ код для вызова всех _различных_ виртуальных
// функций в производных классах (единый интерфейс).
public:
virtual int process (int a) { // Бывает удобно объявлять виртуальные функции базового
// класса чистыми виртуальными (без реализации),
// но тут без тонкостей.
cout << __PRETTY_FUNCTION__ << endl;
return a; // Отсутсвие операции.
};
};
// Далее идут три производных класса, изменяющие реализацию виртуальной функции.
class neg_operation : public unary_operation {
public:
virtual int process (int a) {
cout << __PRETTY_FUNCTION__ << endl;
return -a;
};
};
class abs_operation : public unary_operation {
public:
virtual int process (int a) {
cout << __PRETTY_FUNCTION__ << endl;
return a < 0 ? -a : a;
}
};
class zero_operation : public unary_operation {
public:
virtual int process (int a) {
cout << __PRETTY_FUNCTION__ << endl;
return 0;
}
};
int complex_algorithm (unary_operation* op, int another_argument) {
// Допустим, что в этой функции реализован "очень сложный" алгоритм, код которого
// тщательно проверен и сейчас является стабильным - изменять его крайне нежелательно.
// Как нам расширить возможности алгоритма без внесения изменений?
// ... Тут скрыта "сложная" часть. :)
// Современный подход заставить один и тот же кусок кода (буквально - одну и ту же
// оследовательность символов) делать что-то другое и при этом не изменять ни строчки кода -
// использовать полиморфизм (в данном случае - динамический).
cout << "Function called: " << endl;
// Например, в этой строке компилятор генерирует единственный возможный вариант машинного кода,
// но вызывается функция того объекта, на который указывает указатель переданный в функцию.
int result = op->process (another_argument);
return result;
}
unary_operation* get_operation () { // Эта функция просто создаёт объекты в зависимости от внешних данных.
// В жизни это может быть любой источник объектов. Код именно таких функций
// по идее часто нестабилен и сильно изменяется, но тот, кто с ней работает, уверен,
// что полученный объект "умеет" выполнять все действия из объявленного интерфейса
// (в данном случае - единственную функцию "int process (int)"),
// так как он поддерживает указанный единый интерфейс unary_operation.
int op_code;
cout << "1 - neg;" << endl;
cout << "2 - abs;" << endl;
cout << "3 - zero;" << endl;
cout << "anything else - exit program." << endl;
cout << "Enter desired operation:" << endl;
cin >> op_code;
// Добавьте сюда другую логику.
switch (op_code) {
case 1:
return new neg_operation ();
case 2:
return new abs_operation ();
case 3:
return new zero_operation ();
default:
return 0;
}
};
int main () {
// Выполнение одного и того же алгоритма с различными полиморфными объектами.
int i = 0;
unary_operation* op = get_operation ();
while (op) {
int result = complex_algorithm (op, ++ i);
cout << "Result = " << result << endl;
delete op;
op = get_operation ();
}
return 0;
};
Конечно, без "полиморфизма на уровне ума разработчика" тут тоже не обошлось (функция get_operation()), но без этого никуда — где-то нужно объяснить, на чём будет основываться различное выполнение программы. Зато этот код локализован в
единственной функции. Кроме того, эти ветвления
выполняются один раз, и программа может ползоваться результатом принятого решения (им является объект конкретного класса) сколько угодно, выигрывая в производительности.
Теперь если понадобится ввести новый вид операции, программист просто добавит ещё один класс пронаследованный от unary_operation и немного изменит get_operation. При этом десятки других функций, способных изначально работать с любыми объектами производными от unary_operation, будут это уметь и с объектами нового класса. Идеально, конечно, но к идеалу стремяться...
Это был динамический полиморфизм. Статический полиморфизм в C++ — механизм шаблонов. Это другая тема — реализуется иначе, но эффект один:
// Не изменяя следующий "набор символов", компилятор может приводить к
// выполнению различных действий (в зависимости от типа объекта).
template < class T >
void func (T& obj) {
// ...
obj.process ();
// ...
}
Особое свойство статического полиморфизма — всевозможные методы оптимизации компилятором, а также целое направление — generic programing.
Особое свойство динамического полиморфизма — возможность только частичной перекомпиляции кода.
Готов прокомментировать, если не понятны детали.