Re: динамический полиморфизм, где он? и зачем он нужен?
От: MTDIGSUT  
Дата: 10.06.07 23:30
Оценка: 15 (4) +1
Здравствуйте, Аноним, Вы писали:

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


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.
Особое свойство динамического полиморфизма — возможность только частичной перекомпиляции кода.

Готов прокомментировать, если не понятны детали.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.