Поліморфізм у С ++


129

AFAIK:

C ++ забезпечує три різні види поліморфізму.

  • Віртуальні функції
  • Перенавантаження назви функції
  • Перевантаження оператора

Крім перерахованих вище трьох типів поліморфізму, існують і інші види поліморфізму:

  • час виконання
  • час складання
  • спеціальний поліморфізм
  • параметричний поліморфізм

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

Але для двох інших

спеціальний поліморфізм:

Якщо діапазон фактичних типів, які можна використовувати, є обмеженим, і комбінації повинні бути окремо визначені перед використанням, це називається спеціальним поліморфізмом.

параметричний поліморфізм:

Якщо весь код написаний без згадки про якийсь конкретний тип і, таким чином, його можна прозоро використовувати з будь-якою кількістю нових типів, це називається параметричним поліморфізмом.

Я навряд чи можу їх зрозуміти :(

хтось може пояснити їх обом, якщо можливо, прикладом? Я сподіваюся, що відповіді на ці запитання будуть корисними для багатьох нових запитань з їхніх коледжів.


30
Власне, C ++ має чотири види поліморфізму: параметричний (загальність за допомогою шаблонів на C ++), включення (підтипування за допомогою віртуальних методів у C ++), перевантаження та примушування (неявні перетворення). Зазвичай мало розмежування між перевантаженням функцій та перевантаженням оператора.
fredoverflow

Тож здається, що згадуваний я веб-сайт багатьох вводить в оману .. я прав?
Віджай

@zombie: цей веб-сайт торкається безлічі хороших концепцій, але не є точним і послідовним у використанні термінології (наприклад, після того, як він починає говорити про віртуальний диспетчер / поліморфізм виконання, він робить багато тверджень про поліморфізм, який є неправильним. в цілому, але вірно для віртуальної відправки). Якщо ви вже розумієте тему, ви можете звернутися до сказаного і подумки вставити необхідні застереження, але важко потрапити туди, прочитавши сайт ....
Тоні Делрой

Деякі терміни є близькими до синонімів або більше стосуються, але більш обмежені, ніж інші. Наприклад, на моєму досвіді в Haskell в основному використовується термін "спеціальний поліморфізм", але "віртуальні функції" дуже тісно пов'язані. Незначна різниця полягає в тому, що "віртуальні функції" - це об'єктно-орієнтований термін, що відноситься до функцій-членів із "пізнім прив'язкою". "Багаторазова розсилка" - це також своєрідний поліморфізм. І як каже FredOverflow, і перевантаження оператора, і функції - це в основному одне і те ж.
Steve314

Я зафіксував ваше форматування для вас. Прочитайте довідку, розташовану праворуч від області редагування. Хтось із> 200 запитаннями та> 3 к. Повинен знати цей основний матеріал. Також ви можете придбати нову клавіатуру. Здається, клавіша перемикання періодично виходить з ладу. О, і: у C ++ немає такого поняття, як "шаблонна функція" . Однак існують шаблони функцій .
sbi

Відповіді:


219

Розуміння / вимоги до поліморфізму

Щоб зрозуміти поліморфізм - як цей термін використовується в обчислювальній науці - це допомагає почати з простого тесту та його визначення. Поміркуйте:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Тут f()потрібно виконати деяку операцію і їй надаються значення xі yяк входи.

Щоб виявити поліморфізм, f()необхідно вміти працювати зі значеннями щонайменше двох різних типів (наприклад, intта double), знаходячи та виконуючи окремий відповідний типу код.


С ++ механізми поліморфізму

Явний поліморфізм, визначений програмістом

Ви можете написати f()так, що він може працювати на декількох типах будь-яким із наступних способів:

  • Попередня обробка:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
    
  • Перевантаження:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • Шаблони:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • Віртуальна відправка:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch
    

Інші пов'язані з цим механізми

Поліморфізм, що надається компілятором для вбудованих типів, стандартні перетворення та кастинг / примус обговорюються пізніше для повноти:

  • вони зазвичай інтуїтивно зрозуміли все одно (гарантуючи " о, та " реакцію),
  • вони впливають на поріг у вимаганні та безперебійності використання вищезазначених механізмів та
  • пояснення - це відверте відволікання від важливіших понять.

Термінологія

Подальша категоризація

Враховуючи вищезгадані поліморфні механізми, ми можемо їх класифікувати різними способами:

  • Коли вибирається поліморфний специфічний код?

    • Час виконання означає, що компілятор повинен генерувати код для всіх типів, з якими програма може працювати під час запуску, а під час виконання вибирається правильний код ( віртуальна відправка )
    • Час компіляції означає вибір коду, характерного для типу, під час компіляції. Наслідок цього: скажіть, що програма, названа fвище, intаргументами - залежно від використовуваного поліморфного механізму та вбудованих варіантів, компілятор може уникнути генерування будь-якого коду для цього f(double), або згенерований код може бути викинутий у якийсь момент компіляції чи зв’язування. ( усі механізми вище, крім віртуальної відправки )

  • Які типи підтримуються?

    • Спеціальний сенс, який ви надаєте явний код для підтримки кожного типу (наприклад, перевантаження, спеціалізація шаблонів); ви явно додаєте підтримку типу "для цього" (відповідно до значення спеціального значення), якесь інше "це" і, можливо, "теж" ;-).
    • Параметричне значення Ви можете просто спробувати використовувати функцію для різних типів параметрів, не роблячи нічого конкретного, щоб увімкнути їх підтримку (наприклад, шаблони, макроси). Об'єкт з функціями / операторами, які діють як шаблон / макрос очікує 1 - це все, що потрібно шаблону / макросу виконувати свою роботу, причому точний тип не має значення. "Концепції", запроваджені C ++ 20, висловлюють та здійснюють такі очікування - див. Сторінку cppreference тут .

      • Параметричний поліморфізм забезпечує типізацію качок - концепцію, приписувану Джеймсу Віткомбу Райлі, який, мабуть, сказав: "Коли я бачу птаха, який ходить як качка і плаває, як качка, і рикає, як качка, я називаю цю птицю качкою". .

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • Поліморфізм підтипу (він же включення) дозволяє працювати над новими типами без оновлення алгоритму / функції, але вони повинні бути отримані з того ж базового класу (віртуальна відправка)

1 - Шаблони надзвичайно гнучкі. SFINAE (див. Також std::enable_if) ефективно дозволяє кілька наборів очікувань щодо параметричного поліморфізму. Наприклад, ви можете кодувати, що коли тип даних, який ви обробляєте, є .size()членом, ви будете використовувати одну функцію, інакше інша функція, яка не потрібна .size()(але, мабуть, певною мірою страждає - наприклад, використовуючи повільніше strlen()або не друкуючи як корисне повідомлення в журналі). Ви також можете вказати особливі поведінки, коли шаблон інстанціюється певними параметрами, або залишаючи деякі параметри параметричними ( часткова спеціалізація шаблону ), або ні ( повна спеціалізація ).

"Поліморфний"

Альф Штейнбах зауважує, що в стандарті C ++ поліморфний посилається лише на поліморфізм під час запуску за допомогою віртуальної диспетчеризації. Загальний склад. Наук. сенс є більш вражаючим, відповідно до словника творця C ++ Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):

поліморфізм - надання єдиного інтерфейсу для сутностей різних типів. Віртуальні функції забезпечують динамічний (під час виконання) поліморфізм через інтерфейс, що надається базовим класом. Перевантажені функції та шаблони забезпечують статичний (час компіляції) поліморфізм. TC ++ PL 12.2.6, 13.6.1, D&E 2.9.

Ця відповідь - як і питання - стосується функцій C ++ до Comp. Наук. термінологія.

Обговорення

За допомогою стандарту C ++, використовуючи більш вузьке визначення поняття "поліморфізм", ніж порівняння. Наук. спільноти, щоб забезпечити взаєморозуміння вашої аудиторії, врахуйте ...

  • використовуючи однозначну термінологію ("чи можемо ми зробити цей код повторним використання для інших типів?" або "чи можемо ми використовувати віртуальну диспетчеризацію?", а не "чи можемо ми зробити цей код поліморфним?") та / або
  • чітко визначаючи свою термінологію.

І все-таки, що важливо бути великим програмістом на C ++ - це зрозуміти, що насправді робить поліморфізм для вас ...

    дозволяє один раз написати "алгоритмічний" код, а потім застосувати його до багатьох типів даних

... і тоді будьте в курсі того, як різні поліморфні механізми відповідають вашим реальним потребам.

Поліморфізм під час виконання:

  • вхід, оброблений фабричними методами, і виплеснутий як неоднорідний збір об'єктів, що обробляється через Base*s,
  • реалізація, вибрана під час виконання на основі файлів конфігурації, комутаторів командного рядка, налаштувань інтерфейсу тощо,
  • реалізація варіюється під час виконання, наприклад, для моделі машинного стану.

Коли немає чіткого драйвера для поліморфізму під час виконання, часто є кращими варіанти компіляції. Поміркуйте:

  • аспект компіляції, що називається, шаблонні класи є кращими для жирових інтерфейсів, які виходять з ладу під час виконання
  • SFINAE
  • CRTP
  • оптимізації (багато з них: усунення вбудованого та мертвого коду, розгортання циклу, масиви на основі статичних стеків та купи)
  • __FILE__, __LINE__, Строковий літерал конкатенації і інші унікальні можливості макросів (які залишаються злим ;-))
  • підтримується семантичне використання шаблонів і макросів тестового використання, але не обмежуйте штучно способом надання такої підтримки (як правило, віртуальна відправка вимагає точно збігати функції учасника)

Інші механізми, що підтримують поліморфізм

Як було обіцяно, для повноти висвітлюються декілька периферійних тем:

  • надані компілятором перевантаження
  • конверсії
  • касти / примус

Ця відповідь завершується обговоренням того, як вищезгадане поєднується для розширення можливостей та спрощення поліморфного коду - особливо параметричного поліморфізму (шаблони та макроси).

Механізми відображення для операцій, характерних для типу

> Неявні перевантаження, передбачені компілятором

Концептуально компілятор перевантажує багато операторів для вбудованих типів. Він концептуально не відрізняється від визначеної користувачем перевантаження, але перерахований у списку, оскільки його легко не помітити. Наприклад, ви можете додавати в ints і doubles, використовуючи однакові позначення, x += 2і компілятор створює:

  • інструкції щодо процесорного типу
  • результат того ж типу.

Потім перевантаження безперешкодно поширюється на визначені користувачем типи:

std::string x;
int y = 0;

x += 'c';
y += 'c';

Компілятор, що надається компілятором, для основних типів є звичайним для комп'ютерних мов високого рівня (3GL +), а явне обговорення поліморфізму загалом передбачає щось більше. (2GLs - мови монтажу - часто вимагають від програміста явно використовувати різні мнемоніки для різних типів.)

> Стандартні конверсії

Четвертий розділ стандарту C ++ описує стандартні перетворення.

Перший пункт добре підсумовує (зі старої чернетки - сподіваємось, все ще суттєво правильний):

-1- Стандартні перетворення - це неявні перетворення, визначені для вбудованих типів. Стаття conv перераховує повний набір таких перетворень. Стандартна послідовність перетворень - це послідовність стандартних перетворень у такому порядку:

  • Нульова або одна конверсія з наступного набору: перетворення значення-в-значення, перетворення масиву в вказівник та перетворення функції на вказівник.

  • Нульова або одна конверсія з наступного набору: інтегральні акції, просування з плаваючою комою, інтегральні перетворення, перетворення з плаваючою комою, плаваючо-інтегральні перетворення, конверсії вказівника, перетворення вказівника на члени та булеві перетворення.

  • Нульова або одна кваліфікаційна конверсія.

[Примітка: стандартна послідовність перетворень може бути порожньою, тобто вона може складатися з перетворень. ] Для вираження буде застосовано стандартну послідовність перетворення для перетворення його в потрібний тип призначення.

Ці конверсії дозволяють код, наприклад:

double a(double x) { return x + 2; }

a(3.14);
a(42);

Застосування більш раннього тесту:

Щоб бути поліморфним, [ a()] повинен вміти працювати зі значеннями щонайменше двох різних типів (наприклад, intта double), знаходячи та виконуючи відповідний типу код .

a()сам запускає код спеціально для double, тому не є поліморфним.

Але, у другому виклику a()компілятор знає , для створення типу-відповідний код для «розкрутки з плаваючою точкою» (Standard § 4) , щоб конвертувати 42в 42.0. Цей додатковий код знаходиться у функції виклику . Значення цього ми обговоримо у висновку.

> Примус, касти, неявні конструктори

Ці механізми дозволяють визначеним користувачем класам визначати поведінку, подібну до стандартних перетворень типів вбудованих типів. Давай подивимось:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

Тут об'єкт std::cinоцінюється в булевому контексті за допомогою оператора перетворення. Це можна концептуально згрупувати з "інтегральними рекламними пропозиціями" та ін. Зі стандартних перетворень у вищевказаній темі.

Неявні конструктори ефективно роблять те саме, але керуються типом "литий тип":

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

Наслідки перевантажень, перетворень та примусу, передбачених компілятором

Поміркуйте:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

Якщо ми хочемо, щоб сума xбула розглянута як реальна кількість під час поділу (тобто 6,5, а не округлена до 6), нам потрібно лише змінити typedef double Amount.

Це приємно, але не було б занадто багато роботи, щоб зробити код явно "типу правильним":

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

Але врахуйте, що ми можемо перетворити першу версію в template:

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

Через ті невеликі "зручності", що їх можна так легко встановити для роботи intабо doubleпрацювати за призначенням. Без цих функцій нам знадобляться чіткі касти, риси типу та / або класи політик, деякий багатослівний, схильний до помилок приклад:

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

Отже, перевантаження завантажених оператором для вбудованих типів, стандартні перетворення, конструктори лиття / примусу / неявні конструктори - всі вони сприяють тонкій підтримці поліморфізму. З визначення у верхній частині цієї відповіді вони адресують "пошук та виконання відповідного типу коду" шляхом зіставлення:

  • "від" від типів параметрів

    • від багатьох типів даних поліморфний алгоритмічний код

    • для коду, написаного для (потенційно меншої) кількості (тих самих чи інших) типів.

  • "до" параметричних типів від значень постійного типу

Вони не встановлюють поліморфні контексти самостійно, але допомагають розширювати / спрощувати код у таких контекстах.

Ви можете відчути себе обдуреним ... це здається не дуже. Важливість полягає в тому, що в параметричних поліморфних контекстах (тобто всередині шаблонів або макросів) ми намагаємось підтримувати довільно великий діапазон типів, але часто хочемо виражати операції над ними з точки зору інших функцій, літералів та операцій, призначених для невеликий набір типів. Це зменшує необхідність створення майже однакових функцій або даних на основі типу, коли операція / значення логічно однакові. Ці функції співпрацюють, щоб додати ставлення до "найкращих зусиль", роблячи те, що інтуїтивно очікується, використовуючи обмежені доступні функції та дані та зупиняючись із помилкою лише тоді, коли існує справжня неоднозначність.

Це допомагає обмежити потребу в поліморфному коді, що підтримує поліморфний код, затягуючи більш щільну мережу навколо використання поліморфізму, тому локалізоване використання не вимагає широкого використання, а надання переваг поліморфізму доступним у міру необхідності, не покладаючи витрат на необхідність піддавати реалізації на час компіляції, мати кілька копій однієї і тієї ж логічної функції в об'єктному коді для підтримки використовуваних типів і при виконанні віртуальної диспетчери на відміну від вбудованих або принаймні вирішених під час компіляції викликів. Як це характерно для C ++, програмісту надається велика свобода контролювати межі, в межах яких використовується поліморфізм.


1
-1 Прекрасна відповідь, крім термінологічної дискусії. Стандарт C ++ визначає термін "поліморфний" в §1.8 / 1, там посилаючись на розділ 10.3 про віртуальні функції. Таким чином, немає кімнати для хитання, немає місця для обговорення, немає місця для особистої думки: в контексті стандарту C ++ цей термін визначається раз і назавжди. І це справді грає на практиці. Наприклад, § 5.2.7 / 6 про dynamic_castвимагає "вказівника на або значення поліморфного типу". Cheers & hth.,
Ура та хт. - Альф

@Alf: чудова посилання - хоча я думаю, що ваша перспектива занадто вузька. З питання, що перераховує перевантаження, спеціальний та параметричний поліморфізм тощо., Зрозуміло, що відповідь має співвідносити можливості C ++ із загальним комп. Наук. значення термінів. Справді, словник Stroustrup говорить про "поліморфізм - надання єдиного інтерфейсу для сутностей різних типів. Віртуальні функції забезпечують динамічний (під час виконання) поліморфізм через інтерфейс, який надається базовим класом. Перевантажені функції та шаблони забезпечують статичний (компільований) поліморфізм. TC ++ PL 12.2.6, 13.6.1, D&E 2.9. "
Тоні Делрой

@Tony: це не головна суть вашої відповіді. це нормально, це здорово. це просто та Wrt. термінологію ви отримали назад: формальна академічна термінологія - це вузька, визначена Святим міжнародним стандартом, а неофіційна груба термінологія, де люди можуть означати дещо різні речі, - це в основному використовується в цьому запитанні та відповіді. Cheers & hth.,
Ура та хт. - Альф

@Alf: Я хотів би, щоб відповідь була чудовою - "Інші механізми" потрібно переписати в п’ятій частині рядків, і я замислююсь / розробляю більш конкретні риси та наслідки, які протиставлять поліморфні механізми. У будь-якому разі, я розумію, що формально-академічний виключно зміст, орієнтований на С ++, може бути вузьким, але формальний академічний загальний склад. Наук. сенсу немає, як свідчить словник Струструпа. Нам потрібно щось остаточне - наприклад, визначення від Кнут - ще не пощастило. Я вдячний, що ти гуру С ++, але чи можеш ти конкретно вказати на це?
Тоні Делрой

1
@Alf: по-друге, я впевнений, що поліморфізм формально визначений у будь-якому пристойному загальному комп. Наук. книга (вічний, стабільний) спосіб, сумісний з моїм використанням (та Stroustrup's). Стаття у Вікіпедії посилається на декілька наукових публікацій, які визначають це так: "Поліморфні функції - це функції, операнди (фактичні параметри) можуть мати більше одного типу. Поліморфні типи - це типи, операції яких застосовні до значень більше одного типу". (від lucacardelli.name/Papers/OnUnderstanding.A4.pdf ). Отже, питання "хто виступає за Comp. Sci" ...?
Тоні Делрой

15

У C ++ важливим відмінністю є прив'язка до часу виконання та компіляції. Спеціальні та параметричні не дуже допомагають, як я поясню пізніше.

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

Зауважте - поліморфізм під час виконання може все-таки вирішуватися під час компіляції, але це лише оптимізація. Необхідність ефективно підтримувати розв’язання часу роботи та торгуватися іншими проблемами - це частина того, що призвело до того, що віртуальні функції є такими, якими вони є. І це дійсно ключово для всіх форм поліморфізму в С ++ - кожен виникає з різних наборів компромісів, здійснених в іншому контексті.

Перевантаження функцій та перевантаження оператора - це одне і те ж саме, що має значення. Назви та синтаксис їх використання не впливають на поліморфізм.

Шаблони дозволяють одночасно вказати багато перевантажень функції.

Існує ще один набір імен для тієї ж ідеї часу вирішення ...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

Ці імена більше асоціюються з OOP, тому трохи дивно говорити, що шаблон або інша нечленова функція використовує раннє прив'язування.

Щоб краще зрозуміти взаємозв'язок між віртуальними функціями та перевантаженням функцій, також корисно зрозуміти різницю між "разовою відправленням" та "багаторазовою відправленням". Ідею можна зрозуміти як прогресію ...

  • По-перше, є мономорфні функції. Реалізація функції однозначно ідентифікується за назвою функції. Жоден із параметрів не є спеціальним.
  • Потім відбувається разова відправка. Один з параметрів вважається спеціальним і використовується (разом з назвою) для визначення, яку реалізацію використовувати. В OOP ми схильні розглядати цей параметр як "об'єкт", перераховуємо його перед назвою функції тощо.
  • Потім відбувається багаторазова відправка. Будь-які / всі параметри сприяють визначенню, яку реалізацію використовувати. Тому, ще раз, жоден з параметрів не повинен бути спеціальним.

ОПО, очевидно, більше, ніж привід призначити один параметр як спеціальний, але це одна його частина. Що стосується того, що я сказав про компроміси - одноразове відправлення досить легко зробити ефективно (звичайна реалізація називається "віртуальні таблиці"). Багаторазове відправлення більш незручне, не тільки з точки зору ефективності, але і для окремої компіляції. Якщо вам цікаво, ви можете знайти "проблему з виразом".

Як і трохи дивно використовувати термін "раннє прив'язування" для функцій, які не є членами, трохи дивно використовувати терміни "разова відправка" та "множинна відправка", де поліморфізм вирішується під час компіляції. Зазвичай вважається, що C ++ не має багаторазового відправлення, що вважається особливим видом роздільної здатності. Однак перевантаження функцій може розглядатися як багаторазова диспетчеризація, здійснена під час компіляції.

Повертаючись до параметричного порівняно з спеціальним поліморфізмом, ці терміни є більш популярними у функціональному програмуванні, і вони не дуже працюють у С ++. Навіть так...

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

Спеціальний поліморфізм є тимчасовим, оскільки ви надаєте різний код залежно від конкретних типів.

Перевантаження та віртуальні функції - це приклади спеціального поліморфізму.

Знову ж таки, є деякі синоніми ...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

За винятком цих не зовсім синонімів, хоча вони зазвичай трактуються так, ніби вони були, і саме там C ++ може виникнути плутанина.

Міркування, що трактує їх як синоніми, полягає в тому, що, обмежуючи поліморфізм певними класами типів, стає можливим використання операцій, характерних для цих класів типів. Слово "класи" тут можна інтерпретувати в значенні OOP, але насправді просто стосується (зазвичай названих) наборів типів, які поділяють певні операції.

Тому параметричний поліморфізм зазвичай приймається (принаймні за замовчуванням), щоб мати на увазі необмежений поліморфізм. Оскільки той самий код використовується незалежно від параметрів типу, єдиними підтримуються операціями є ті, які працюють для всіх типів. Залишаючи набір типів без обмежень, ви суворо обмежуєте набір операцій, які ви можете застосувати до цих типів.

Наприклад, Haskell, ви можете мати ...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

aТут є невимушеним поліморфним типом. Це може бути все що завгодно, тому ми не можемо багато що робити зі значеннями цього типу.

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

Тут aобмежується бути членом Numкласу - типи, які діють як числа. Це обмеження дозволяє робити речі з числами з тими значеннями, як-от додавати їх. Навіть 3поліморфний тип висновку визначає, що ви маєте на увазі 3тип a.

Я вважаю це обмеженим параметричним поліморфізмом. Є лише одна реалізація, але вона може застосовуватися лише у обмежених випадках. Спеціальний аспект - вибір +і 3використання. Кожен "екземпляр" Numмає свою чітку реалізацію. Тож навіть у Haskell "параметричні" та "необмежені" насправді не є синонімами - не звинувачуйте мене, це не моя вина!

У C ++ і перевантаження, і віртуальні функції є спеціальним поліморфізмом. Визначення спеціального поліморфізму не має значення, чи реалізація обрана під час виконання або під час компіляції.

C ++ дуже близький до параметричного поліморфізму з шаблонами, якщо кожен параметр шаблона має тип typename. Є параметри типу, і є одна реалізація незалежно від того, які типи використовуються. Однак правило "Зміна заміни не помилка" означає, що неявні обмеження виникають в результаті використання операцій всередині шаблону. Додаткові ускладнення включають спеціалізацію шаблонів для надання альтернативних шаблонів - різні (спеціальні) реалізації.

Таким чином, C ++ має параметричний поліморфізм, але він неявно обмежений і може бути відмінений спеціальними альтернативами - тобто ця класифікація насправді не працює для C ++.


+1 Багато цікавих моментів та розумінь. Я провів лише кілька годин, читаючи про Haskell, так що " aтут є нестримний поліморфний тип [...], тому ми не можемо багато зробити зі значеннями цього типу". викликало інтерес - у C ++ sans Concepts ви не обмежуєтесь лише спробами певного набору операцій на аргументі типу, визначеного як параметр шаблону ... бібліотеки, як концепції boost, працюють іншим способом - переконайтеся, що тип підтримує операції ви вказуєте, а не оберігаєте від випадкового використання додаткових операцій.
Тоні Делрой

@Tony - Поняття - це спосіб явного обмеження поліморфізму шаблонів. Неявні обмеження, очевидно, не зникнуть через сумісність, але явні обмеження, безумовно, значно покращать речі. Я впевнений, що деякі минулі плани концепцій були дещо пов'язані з типовими класами Haskell, хоча я не заглядав у них так глибоко, і коли я востаннє дивився "дрібно", я не знав багато Haskell.
Steve314

"Неявні обмеження, очевидно, не зникнуть із-за сумісності" - з пам'яті, C ++ 0x концепції зробили (обіцяють: - /) запобігти "неявні обмеження" - ви могли використовувати тип лише способами, обіцяними Концептами.
Тоні Делрой

2

Щодо спеціального поліморфізму, це означає перевантаження функцій або перевантаження оператора. Ознайомтесь тут:

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

Щодо параметричного поліморфізму, то шаблонні функції можна також врахувати, оскільки вони не обов'язково приймають параметри фіксованих типів. Наприклад, одна функція може сортувати масив цілих чисел, а також може сортувати масив рядків тощо.

http://en.wikipedia.org/wiki/Parametric_polymorphism


1
На жаль, хоча це правильно, це вводить в оману. Функції шаблону можуть отримати неявні обмеження через правило SFINAE - використання операції в шаблоні неявно обмежує поліморфізм - і спеціалізація шаблонів може надати спеціальні альтернативні шаблони, що перекривають більш загальні шаблони. Таким чином, шаблон (за замовчуванням) забезпечує необмежений параметричний поліморфізм, але цього не застосовується - принаймні два шляхи можуть бути обмеженими або тимчасовими.
Стів314

Насправді ваш приклад - сортування - передбачає обмеження. Сортування працює лише для впорядкованих типів (тобто надають <оператори та подібні). В Haskell ви б висловили цю вимогу явно, використовуючи клас Ord. Той факт, що ви отримуєте різну <залежність від конкретного типу (як це надається примірником Ord), вважатиметься спеціальним поліморфізмом.
Steve314

2

Це може не допомогти, але я зробив це для того, щоб познайомити своїх друзів з програмуванням, видавши визначені функції, як START, наприклад , і ENDдля основної функції, щоб це не було надто складно (вони використовували лише файл main.cpp ). Він містить поліморфні класи та структури, шаблони, вектори, масиви, препроцесорні директиви, дружбу, оператори та покажчики (все, що ви, напевно, повинні знати, перш ніж спробувати поліморфізм):

Примітка. Це не закінчено, але ви можете отримати ідею

main.cpp

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

головний.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};

1

Ось основний приклад з використанням поліморфних класів

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}

0

Поліморфізм означає багато форм, оскільки вони використовуються для того, щоб оператор діяв по-різному в різних випадках. Поліморфізм використовується для здійснення спадкування. Наприклад, ми визначили fn draw () для форми класу, тоді fn малюнок може бути реалізований для малювання кола, коробки, трикутника та інших фігур. (які є предметами класової форми)


-3

Якщо хтось скаже CUT цим людям

The Surgeon
The Hair Stylist
The Actor

Що станеться?

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

Отже, вище представлення показує, що таке поліморфізм (однойменна назва, різна поведінка) в ООП.

Якщо ви збираєтесь на співбесіду, а інтерв'юер просить вам розповісти / показати живий приклад поліморфізму в тій же кімнаті, в якій ми сидимо, скажімо,

Відповідь - двері / вікна

Цікаво, як?

Через двері / вікно - людина може прийти, повітря може прийти, світло, може прийти дощ і т.д.

тобто одна форма іншої поведінки (поліморфізм).

Щоб зрозуміти це краще і просто, я використав вище приклад .. Якщо вам потрібна посилання на код, дотримуйтесь наведених вище відповідей.


Як я вже згадував для кращого розуміння поліморфізму в c ++, я використовував вище приклад. Це може допомогти свіжіше насправді зрозуміти і співвіднести, який сенс або те, що відбувається за кодом, виконуючи під час співбесіди. Дякую!
Санчіт

op запитав "поліморфізм у с ++". Ваша відповідь занадто абстрактна.
StahlRat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.