Полиморфизм в C ++


129

НАСКОЛЬКО МНЕ ИЗВЕСТНО:

C ++ предоставляет три различных типа полиморфизма.

  • Виртуальные функции
  • Перегрузка имени функции
  • Перегрузка оператора

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

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

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

Но для двух других

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

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

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

параметрический полиморфизм:

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

Я их с трудом понимаю :(

кто-нибудь может объяснить их оба, если возможно, на примере? Я надеюсь, что ответы на этот вопрос будут полезны для многих выпускников колледжей.


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

Похоже, что упомянутый мной веб-сайт многих вводит в заблуждение. Я прав?
Виджай

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

Некоторые термины являются почти синонимами или более связаны, но более ограничены, чем другие термины. Например, по моему опыту, термин «специальный полиморфизм» в основном используется в Haskell, но «виртуальные функции» очень тесно связаны. Незначительное отличие состоит в том, что «виртуальные функции» - это объектно-ориентированный термин, относящийся к функциям-членам с «поздним связыванием». «Множественная отправка» также является разновидностью произвольного полиморфизма. И, как говорит FredOverflow, перегрузка операторов и функций в основном одно и то же.
Steve314

Я исправил для вас форматирование. Прочтите справку справа от панели редактирования. Кто-то, у кого> 200 вопросов и> 3k, должен знать эти базовые вещи. Кроме того, вы можете купить новую клавиатуру. Кажется, что эта клавиша Shift периодически выходит из строя. Да, и еще: в C ++ не существует такой вещи, как "функция шаблона" . Однако есть шаблоны функций .
sbi

Ответы:


219

Понимание / требования к полиморфизму

Чтобы понять полиморфизм - как этот термин используется в компьютерных науках - нужно начать с простого теста и определения его. Рассматривать:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Здесь f()нужно выполнить некоторую операцию и получить значения xи в yкачестве входных данных.

Чтобы проявить полиморфизм, f()необходимо уметь работать со значениями по крайней мере двух различных типов (например, intи double), находя и выполняя отдельный код, соответствующий типу.


C ++ механизмы полиморфизма

Явный полиморфизм, указанный программистом

Вы можете написать так 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), либо сгенерированный код может быть отброшен в какой-то момент компиляции или компоновки. ( все вышеперечисленные механизмы, кроме виртуальной отправки )

  • Какие типы поддерживаются?

    • Ad-hoc означает, что вы предоставляете явный код для поддержки каждого типа (например, перегрузка, специализация шаблона); вы явно добавляете поддержку типа «для этого» (в соответствии со значением ad hoc ), некоторых других «это» и, возможно, «того» тоже ;-).
    • Параметрический смысл означает, что вы можете просто попробовать использовать функцию для различных типов параметров, не делая ничего специально, чтобы включить ее поддержку для них (например, шаблоны, макросы). Объект с функциями / операторами, которые действуют так, как ожидает шаблон / макрос 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 ++ полиморфизм относится только к полиморфизму времени выполнения с использованием виртуальной диспетчеризации. General Comp. Sci. значение более инклюзивное, согласно глоссарию создателя C ++ Бьярна Страуструпа ( http://www.stroustrup.com/glossary.html ):

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

Этот ответ - как и вопрос - связывает функции C ++ с Comp. Sci. терминология.

обсуждение

В стандарте C ++ используется более узкое определение «полиморфизма», чем в Comp. Sci. сообщества, чтобы обеспечить взаимопонимание для вашей аудитории, рассмотрите ...

  • используя однозначную терминологию («можем ли мы сделать этот код повторно используемым для других типов?» или «можем ли мы использовать виртуальную диспетчеризацию?» вместо «можем ли мы сделать этот код полиморфным?») и / или
  • четко определяя вашу терминологию.

Тем не менее, для того, чтобы стать отличным программистом на C ++, важно понимать, что на самом деле делает для вас полиморфизм ...

    позволяя вам написать "алгоритмический" код один раз, а затем применить его ко многим типам данных

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

Подходит для полиморфизма во время выполнения:

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

Когда нет четкого драйвера для полиморфизма времени выполнения, часто предпочтительнее использовать параметры времени компиляции. Рассматривать:

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

Другие механизмы, поддерживающие полиморфизм

Как и было обещано, для полноты картины освещены несколько второстепенных тем:

  • предоставляемые компилятором перегрузки
  • преобразования
  • слепки / принуждение

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

Механизмы отображения на операции, зависящие от типа

> Неявные перегрузки, предоставляемые компилятором

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

  • инструкции ЦП, зависящие от типа
  • результат того же типа.

Затем перегрузка легко распространяется на определяемые пользователем типы:

std::string x;
int y = 0;

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

Предоставляемые компилятором перегрузки для базовых типов распространены в компьютерных языках высокого уровня (3GL +), и явное обсуждение полиморфизма обычно подразумевает нечто большее. (2GL - языки ассемблера - часто требуют, чтобы программист явно использовал разные мнемоники для разных типов.)

> Стандартные преобразования

Четвертый раздел стандарта C ++ описывает стандартные преобразования.

Первый пункт хорошо резюмируется (из старого черновика - надеюсь, все еще в основном правильного):

-1- Стандартные преобразования - это неявные преобразования, определенные для встроенных типов. Предложение conv перечисляет полный набор таких преобразований. Стандартная последовательность преобразования - это последовательность стандартных преобразований в следующем порядке:

  • Ноль или одно преобразование из следующего набора: преобразование lvalue-to-rvalue, преобразование массива в указатель и преобразование функции в указатель.

  • Ноль или одно преобразование из следующего набора: интегральные продвижения, продвижение с плавающей запятой, интегральные преобразования, преобразования с плавающей запятой, преобразования с плавающей запятой, преобразования указателя, преобразования указателя в член и преобразования логического типа.

  • Ноль или одно преобразование квалификации.

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

Эти преобразования позволяют использовать такой код, как:

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оценивается в логическом контексте с помощью оператора преобразования. Это может быть концептуально сгруппировано с «интегральными рекламными предложениями» и др. Из стандартных преобразований в теме выше.

Неявные конструкторы фактически делают то же самое, но управляются типом cast-to:

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 about dynamic_castтребует «указателя на или lvalue полиморфного типа». Cheers & hth.,
Cheers and hth. - Alf

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

@ Тони: это не главное, что ваш ответ неверен. это нормально, это здорово. это просто так. терминология, которую вы получили наоборот: формальная академическая терминология - это узкая терминология, определенная Священным международным стандартом, а грубая неформальная терминология, в которой люди могут иметь в виду несколько разные вещи, - это та, которая в основном используется в этом вопросе и ответе. Cheers & hth.,
Cheers and hth. - Alf

@Alf: Хотелось бы, чтобы ответ был отличным - «Другие механизмы» нужно переписать на пятую часть строк, и я обдумываю / разрабатываю более конкретное противопоставление функций и последствий полиморфных механизмов. В любом случае, я понимаю, что формальное академическое значение, ориентированное исключительно на C ++, может быть узким, но формальный академический общий Comp. Sci. значение не является, как свидетельствует глоссарий Страуструпа. Нам нужно что-то окончательное - например, определение от Кнута - пока не удалось погуглить. Я ценю, что вы гуру C ++, но можете ли вы указать на соответствующие доказательства конкретно по этому поводу?
Тони Делрой

1
@Alf: во-вторых, я уверен, что полиморфизм формально определен в любом достойном общем Comp. Sci. книга (вневременной, стабильный) способ, совместимый с моим использованием (и Страуструпом). Статья в Википедии связывает несколько академических публикаций, которые определяют это таким образом: «Полиморфные функции - это функции, операнды которых (фактические параметры) могут иметь более одного типа. Полиморфные типы - это типы, операции которых применимы к значениям более чем одного типа». (из lucacardelli.name/Papers/OnUnderstanding.A4.pdf ). Итак, вопрос в том, "кто говорит от имени Comp. Sci" ...?
Тони Делрой

15

В C ++ важное различие заключается в привязке во время выполнения и во время компиляции. Ad-hoc или параметрический не очень помогают, как я объясню позже.

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

Примечание. Полиморфизм времени выполнения может быть разрешен во время компиляции, но это всего лишь оптимизация. Необходимость в эффективной поддержке разрешения во время выполнения и компромисс с другими проблемами является частью того, что привело к тому, что виртуальные функции стали тем, чем они являются. И это действительно ключ ко всем формам полиморфизма в C ++ - каждая возникает из разных наборов компромиссов, сделанных в разном контексте.

Перегрузка функций и перегрузка операторов - это одно и то же во всех смыслах. Имена и синтаксис их использования не влияют на полиморфизм.

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

Есть еще один набор названий той же идеи времени разрешения ...

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

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

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

  • Во-первых, это мономорфные функции. Реализация функции однозначно идентифицируется по имени функции. Ни один из параметров не является особенным.
  • Тогда есть разовая отправка. Один из параметров считается особым и используется (вместе с именем) для определения того, какую реализацию использовать. В ООП мы склонны рассматривать этот параметр как «объект», перечислять его перед именем функции и т. Д.
  • Затем есть множественная отправка. Любые / все параметры помогают определить, какую реализацию использовать. Поэтому, опять же, ни один из параметров не должен быть особенным.

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

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

Возвращаясь к параметрическому и произвольному полиморфизму, эти термины более популярны в функциональном программировании и не совсем работают в C ++. Несмотря на это...

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

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

Перегрузка и виртуальные функции являются примерами произвольного полиморфизма.

Опять же, есть синонимы ...

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

За исключением того, что это не совсем синонимы, хотя обычно к ним относятся так, как если бы они были, и именно здесь в C ++ может возникнуть путаница.

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

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

Например, в 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вывод полиморфного типа показывает, что вы имеете в виду 3of type a.

Я думаю об этом как об ограниченном параметрическом полиморфизме. Есть только одна реализация, но ее можно применять только в ограниченных случаях. Специальный аспект - это выбор того, что +и 3использовать. Каждый «экземпляр» Numимеет свою собственную реализацию. Так что даже в Haskell «параметрический» и «неограниченный» на самом деле не синонимы - не вините меня, это не моя вина!

В C ++ и перегрузка, и виртуальные функции представляют собой произвольный полиморфизм. Определению специального полиморфизма не важно, выбрана ли реализация во время выполнения или во время компиляции.

C ++ очень близко подходит к параметрическому полиморфизму с шаблонами, если каждый параметр шаблона имеет тип typename. Есть параметры типа и одна реализация, независимо от того, какие типы используются. Однако правило «Ошибка замены не является ошибкой» означает, что неявные ограничения возникают в результате использования операций в шаблоне. Дополнительные сложности включают специализацию шаблона для предоставления альтернативных шаблонов - различных (специальных) реализаций.

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


+1 Много интересных моментов и идей. Я провел всего несколько часов, читая о Haskell, так что « aздесь неограниченный полиморфный тип [...], поэтому мы мало что можем сделать со значениями этого типа». представлял интерес - в C ++ без концепций вы не ограничены только попыткой выполнения определенного набора операций с аргументом типа, указанного в качестве параметра шаблона ... библиотеки, такие как концепции повышения, работают иначе - убедитесь, что тип поддерживает операции вы указываете, а не предохраняетесь от случайного использования дополнительных операций.
Тони Делрой

@Tony - Концепции - это способ явно ограничить полиморфизм шаблонов. Неявные ограничения, очевидно, не исчезнут из-за совместимости, но явные ограничения определенно значительно улучшат ситуацию. Я почти уверен, что некоторые прошлые планы концепций были в некоторой степени связаны с классами типов Haskell, хотя я не изучал их так глубоко, и когда я в последний раз смотрел «поверхностно», я мало знал Haskell.
Steve314

«Неявные ограничения, очевидно, не исчезнут из-за совместимости» - из памяти, C ++ 0x Concepts действительно (обещал: - /) предотвратить «неявные ограничения» - вы могли использовать тип только способами, обещанными концепциями.
Тони Делрой

2

Что касается специального полиморфизма, то это означает перегрузку функции или перегрузки оператора. Посмотрите здесь:

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

Что касается параметрического полиморфизма, шаблонные функции также могут учитываться, потому что они не обязательно принимают параметры ФИКСИРОВАННЫХ типов. Например, одна функция может сортировать массив целых чисел, а также массив строк и т. Д.

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


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

Фактически ваш пример - сортировка - подразумевает ограничение. Сортировка работает только для упорядоченных типов (т. Е. Предоставленных <и подобных операторов). В 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

main.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 () для формы класса, после чего функция draw может быть реализована для рисования круга, прямоугольника, треугольника и других фигур. (которые являются объектами формы класса)


-3

Если кто-нибудь скажет ОТРЕЗАТЬ этим людям

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 ++ я использовал пример выше. Это может помочь новичкам действительно понять и объяснить, что означает или что происходит за кодом во время выступления на собеседовании. Спасибо!
Sanchit

op спросил "полиморфизм в c ++". ваш ответ слишком абстрактен.
StahlRat 03
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.