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

насколько мне известно:

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

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

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

  • времени
  • времени компиляции
  • полиморфизмом
  • параметрический полиморфизм!--6-->

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

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

  • полиморфизмом
  • параметрический полиморфизм the сайт говорит,

полиморфизмом:

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

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

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

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

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

7 ответов


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

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

    Type1 x;
    Type2 y;

    f(x);
    f(y);

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

для демонстрации полиморфизма,f() должен иметь возможность работать со значениями не менее двух distinct типы (например,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
    

другие сопутствующие механизмы

предоставленный компилятором полиморфизм для встроенных типов, стандартных преобразований и литья/принуждения обсуждаются позже для полноты as:

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

терминология

дальнейшая категоризация

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

  • когда выбран полиморфный код конкретного типа?

    • времени означает, что компилятор должен генерировать код для всех типов, которые программа может обрабатывать во время работы, и во время выполнения выбирается правильный код (виртуальный диспетчер)
    • время компиляции означает, что во время компиляции выбирается код конкретного типа. Следствие этого: скажем, программа только называется f выше int аргументы-в зависимости от используемого полиморфного механизма и встроенных вариантов компилятор может избежать генерации любого кода для f(double), или сгенерированный код может быть выброшен в какой-то момент компиляции или компоновки. (все механизмы выше, кроме виртуальной отправки)

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

    • специальной смысл вы предоставляете явный код для поддержки каждого типа (например, перегрузка, специализация шаблона); вы явно добавляете поддержку "для этого" (согласно ad hoc'S означает) тип, некоторые другие "это", и, возможно," это " тоже ;-).
    • параметрический это означает, что вы можете просто попробовать использовать функцию для различных типов параметров, не делая ничего специально, чтобы включить ее поддержку для них (например, шаблоны, макросы). Объект с функциями / операторами, которые действуют как шаблон / макрос ожидает1 is все, что шаблон/макрос должен делать свою работу, при этом точный тип не имеет значения. "Концепции", вырезанные из 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() member вы будете использовать одну функцию, в противном случае другая функция, которая не нуждается .size() (но предположительно страдает каким - то образом-например, используя медленный strlen() или не печатать как полезное сообщение в журнале). Вы также можете указать поведение ad-hoc, когда шаблон создается с определенным параметры, либо оставляя некоторые параметры параметрическими (частичная специализация шаблона) или (полная специализация).

"полиморфных"

Альф Штайнбах комментирует, что в стандарте C++полиморфных относится только к полиморфизму времени выполнения с использованием виртуальной отправки. Генерал Комп. Научный. значение более включено, согласно глоссарию создателя C++ Бьярне Страуструпа (http://www.stroustrup.com/glossary.html):

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

этот ответ-как и вопрос-относится к функциям C++ к Comp. Научный. терминология.

Обсуждение

со стандартом C++, использующим более узкое определение "полиморфизма", чем Comp. Научный. сообщество, чтобы обеспечить взаимопонимание для код зрители считают...

  • используя однозначную терминологию ("можем ли мы сделать этот код многоразовым для других типов?"или" Можем ли мы использовать virtual dispatch?"вместо "можем ли мы сделать этот код полиморфным?"), и/или
  • четкое определение терминология.

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

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

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

костюмы полиморфизма времени выполнения:

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

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

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

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

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

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

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

механизмы сопоставления с операциями конкретного типа

> неявный компилятор-при условии перегрузки

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

  • тип-конкретные инструкции CPU
  • результат того же типа.

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

std::string x;
int y = 0;

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

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

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

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

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

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

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

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

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

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

эти преобразования позволяют код, например:

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

a(3.14);
a(42);

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

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

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

но, во втором вызове a() компилятор знает, как генерировать соответствующий типу код для "продвижения с плавающей запятой" (стандартный §4) для преобразования 42 до 42.0. Этот дополнительный код находится в вызов


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

|----------------------+--------------|
| 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++. Несмотря на это...

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

ad-hoc полиморфизм является ad-hoc в том смысле, что вы предоставляете другой код в зависимости от конкретных типов.

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

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

|------------+---------------|
| 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 class-типы, которые действуют как числа. Это ограничение позволяет выполнять числовые операции с этими значениями, например добавлять их. Даже 3 вывод полиморфного типа выясняет, что вы имеете в виду 3 of тип a.

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

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

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

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


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

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

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

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


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

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

#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);
}

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


Если кто-нибудь скажет вырезать этим людям

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.

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

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

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

Интересно, Как?

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

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

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