Полиморфизм в C ++ - PullRequest
       90

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

122 голосов
/ 02 мая 2011

AFAIK:

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

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

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

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

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

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

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

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

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

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

Я с трудом могу их понять: (

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

Ответы [ 7 ]

211 голосов
/ 02 мая 2011

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

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

    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 ++ 11, помогают выразить и реализовать такие ожидания - будем надеяться, что они превратят его в более поздний Стандарт.

      • Параметрический полиморфизм обеспечивает типирование утки - концепция, приписываемая Джеймсу Уиткомбу Райли, который, по-видимому, сказал "Когда я вижу птицу, которая ходит как утка и плавает, как утка, и крякает как утка, я называю эту птицу уткой. ".

        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 ++ полиморфный относится только к полиморфизму во время выполнения с использованием виртуальной диспетчеризации.Общий комп.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., чтобы обеспечить взаимопонимание для ваша аудитория считает ...

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

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

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

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

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

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

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

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

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

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

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

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

Механизмы отображения на специфичные для типа операции

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

Концептуально, компилятор перегружает множество операторов для встроенных типов.Концептуально он не отличается от указанной пользователем перегрузки, но указан в списке, так как его легко пропустить.Например, вы можете добавить к int s и double s, используя одну и ту же запись x += 2, и компилятор выдаст:

  • специфичные для типа инструкции процессора
  • результаттого же типа.

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

std::string x;
int y = 0;

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

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

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

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

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

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

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

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

  • Ноль или одна квалификация.

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

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

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

a(3.14);
a(42);

Применение более раннего теста:

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

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

Но во втором вызове a() компилятор знает, что нужно сгенерировать соответствующий типу код для«Повышение с плавающей запятой» (Стандарт §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;
}

Итак, перегрузка операторов, предоставляемая компилятором для встроенных типов, Стандартные преобразования, приведение / приведение / неявные конструкторы - все они вносят тонкий вклад в полиморфизм.Из определения в верхней части этого ответа они обращаются к «нахождению и выполнению кода, соответствующего типу» путем сопоставления:

  • «вдали» от типов параметров

    • из много типов данных обрабатывает полиморфный алгоритмический код

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

  • "to" параметрических типов из значений константного типа

Они делают не сами устанавливают полиморфные контексты, но помогают расширить возможности / упростить код в таких контекстах.

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

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

14 голосов
/ 17 ноября 2013

В 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 ++ может возникнуть путаница.

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

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

Например, Хаскель, ты можешь иметь ...

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 имеет свою собственную реализацию этих. Так что даже в Хаскеле «параметрический» и «неограниченный» не являются синонимами - не вините меня, это не моя вина!

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

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

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

2 голосов
/ 27 декабря 2013

Это может быть бесполезно, но я сделал это, чтобы познакомить моих друзей с программированием, выдавая определенные функции, такие как 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;
            }
        }
    }
};
2 голосов
/ 02 мая 2011

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

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

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

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

1 голос
/ 26 декабря 2013

Вот базовый пример использования полиморфных классов

#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 голосов
/ 26 августа 2016

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

0 голосов
/ 15 ноября 2014

Если кто-нибудь скажет 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.

Итак, приведенное выше представление показывает, что такое полиморфизм (то же имя, другое поведение) в ООП.

Если вы идете на собеседование, и интервьюер попросит вас рассказать / показать живой пример полиморфизма в той же комнате, где мы сидим, скажем -

Ответ - Дверь / Окна

Хотите знать, как?

Через Дверь / Окно - человек может прийти, воздух может прийти, свет может прийти, дождь может прийти и т. Д.

то есть одна форма другого поведения (Полиморфизм).

Чтобы понять это лучше и проще, я использовал приведенный выше пример. Если вам нужна ссылка на код, следуйте приведенным выше ответам.

...