sobrecarga simple que programacion por poo polimorfismo multiple herencia c++ polymorphism c++-faq

simple - Polimorfismo en c++



polimorfismo por sobrecarga c++ (7)

Comprensión de / requisitos para el polimorfismo

Para comprender el polimorfismo, como se usa el término en Ciencias de la computación, es útil partir de una simple prueba y definición de este. Considerar:

Type1 x; Type2 y; f(x); f(y);

Aquí, f() es para realizar alguna operación y se le están dando valores x e y como entradas.

Para exhibir polimorfismo, f() debe ser capaz de operar con valores de al menos dos tipos distintos (por ejemplo, int y double ), encontrar y ejecutar un código distintivo apropiado para el tipo.

Mecanismos de C ++ para el polimorfismo

Polimorfismo explícito especificado por el programador

Puede escribir f() manera que pueda operar en varios tipos de cualquiera de las siguientes maneras:

  • Preprocesamiento:

    #define f(X) ((X) += 2) // (note: in real code, use a longer uppercase name for a macro!)

  • Sobrecarga:

    void f(int& x) { x += 2; } void f(double& x) { x += 2; }

  • Plantillas:

    template <typename T> void f(T& x) { x += 2; }

  • Despacho virtual:

    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

Otros mecanismos relacionados

El polimorfismo proporcionado por el compilador para los tipos incorporados, las conversiones estándar y el colado / coerción se analizan más adelante para que esté completo, ya que:

  • por lo general, se los entiende intuitivamente de todos modos (lo que garantiza una reacción " oh, eso "),
  • impactan en el umbral al requerir, y la fluidez en el uso, los mecanismos anteriores, y
  • la explicación es una distracción complicada de conceptos más importantes.

Terminología

Categorización adicional

Dados los mecanismos polimórficos anteriores, podemos categorizarlos de varias maneras:

  • ¿Cuándo se selecciona el código específico del tipo polimórfico?

    • El tiempo de ejecución significa que el compilador debe generar código para todos los tipos que el programa pueda manejar mientras se ejecuta, y en el tiempo de ejecución se selecciona el código correcto ( despacho virtual )
    • El tiempo de compilación significa que la elección del código específico del tipo se realiza durante la compilación. Una consecuencia de esto: supongamos que un programa solo llama f arriba con argumentos int . Dependiendo del mecanismo polimórfico utilizado y las elecciones de delimitación, el compilador puede evitar generar cualquier código para f(double) o el código generado puede descartarse en algún momento de la compilación. o vincular. ( todos los mecanismos anteriores, excepto el despacho virtual )

  • ¿Qué tipos son compatibles?

    • Ad-hoc significa que proporciona un código explícito para soportar cada tipo (por ejemplo, sobrecarga, especialización de plantilla); explícitamente agregas soporte "para esto" (según el significado ad hoc ) tipo, algún otro "esto", y tal vez "eso" también ;-).
    • Esto significa que puede intentar utilizar la función para varios tipos de parámetros sin hacer nada específicamente para habilitar su compatibilidad (p. Ej., Plantillas, macros). Un objeto con funciones / operadores que actúan como la plantilla / macro espera 1 es todo lo que la plantilla / macro necesita para hacer su trabajo, con el tipo exacto siendo irrelevante. Los "conceptos" recortados de C ++ 11 ayudan a expresar y hacer cumplir esas expectativas, esperemos que lo conviertan en un Estándar posterior.

      • El polimorfismo paramétrico proporciona tipeo de pato , un concepto atribuido a James Whitcomb Riley que aparentemente dijo "Cuando veo un pájaro que camina como un pato y nada como un pato y grazna como un pato, lo llamo pato". .

        template <typename Duck> void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); } do_ducky_stuff(Vilified_Cygnet());

    • El polimorfismo de subtipo (también conocido como inclusión) le permite trabajar en tipos nuevos sin actualizar el algoritmo / función, pero deben derivarse de la misma clase base (despacho virtual)

1 - Las plantillas son extremadamente flexibles. SFINAE (ver también std::enable_if ) efectivamente permite varios conjuntos de expectativas para el polimorfismo paramétrico. Por ejemplo, podría codificar que cuando el tipo de datos que está procesando tenga un miembro .size() usará una función, de lo contrario, otra función que no necesita .size() (pero presumiblemente sufre de alguna manera, por ejemplo, usar el strlen() lento strlen() o no imprimir como útil un mensaje en el registro). También puede especificar comportamientos ad-hoc cuando se crea una instancia de la plantilla con parámetros específicos, ya sea dejando algunos parámetros paramétricos ( especialización de plantilla parcial ) o no ( especialización completa ).

"Polimórfico"

Alf Steinbach comenta que en el Estándar polimórfico de C ++ solo se refiere al polimorfismo en tiempo de ejecución que utiliza despacho virtual. Comp. General Sci. el significado es más inclusivo, según el glosario del creador de C ++ Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):

polimorfismo: proporciona una interfaz única para entidades de diferentes tipos. Las funciones virtuales proporcionan un polimorfismo dinámico (en tiempo de ejecución) a través de una interfaz proporcionada por una clase base. Las funciones y plantillas sobrecargadas proporcionan polimorfismo estático (en tiempo de compilación). TC ++ PL 12.2.6, 13.6.1, D y E 2.9.

Esta respuesta, como la pregunta, relaciona las características de C ++ con las Comp. Sci. terminología.

Discusión

Con el Estándar C ++ usando una definición más restringida de "polimorfismo" que el Comp. Sci. comunidad, para asegurar que la comprensión mutua para su audiencia considere ...

  • usando una terminología inequívoca ("¿podemos hacer que este código sea reutilizable para otros tipos?" o "¿podemos usar despacho virtual?" en lugar de "¿podemos hacer que este código sea polimórfico?"), y / o
  • definiendo claramente su terminología.

Aún así, lo que es crucial para ser un gran programador de C ++ es entender qué polimorfismo realmente está haciendo por ti ...

permitiéndole escribir código "algorítmico" una vez y luego aplicarlo a muchos tipos de datos

... y luego sea muy consciente de cómo los diferentes mecanismos polimórficos concuerdan con sus necesidades reales.

Trajes de polimorfismo en tiempo de ejecución:

  • entrada procesada por métodos de fábrica y escupida como una colección heterogénea de objetos manejada a través de Base* s,
  • implementación elegida en tiempo de ejecución basada en archivos de configuración, interruptores de línea de comandos, configuración de UI, etc.
  • la implementación varió en el tiempo de ejecución, como para un patrón de máquina de estado.

Cuando no hay un controlador claro para el polimorfismo en tiempo de ejecución, a menudo son preferibles las opciones de tiempo de compilación. Considerar:

  • el aspecto de compilar-lo que se llama de las clases con plantillas es preferible a las interfaces gordas que fallan en el tiempo de ejecución
  • SFINAE
  • CRTP
  • optimizaciones (muchas incluyendo alineación y eliminación de código muerto, desenrollado de bucles, matrices estáticas basadas en pila frente a montón)
  • __FILE__ , __LINE__ , cadena de concatenación literal y otras capacidades únicas de macros (que siguen siendo malvadas ;-))
  • el uso de semántica de prueba de macros y plantillas es compatible, pero no restringe artificialmente la forma en que se brinda dicha compatibilidad (ya que el despacho virtual tiende a requerir reemplazos de función de miembro que coincidan exactamente)

Otros mecanismos que apoyan el polimorfismo

Como se prometió, para completar, se tratan varios temas periféricos:

  • sobrecargas proporcionadas por el compilador
  • conversiones
  • moldes / coacción

Esta respuesta concluye con una discusión sobre cómo lo anterior se combina para potenciar y simplificar el código polimórfico, especialmente el polimorfismo paramétrico (plantillas y macros).

Mecanismos de mapeo para operaciones específicas de tipo

> Sobrecargas implícitas proporcionadas por el compilador

Conceptualmente, el compilador sobrecarga muchos operadores para tipos incorporados. No es conceptualmente diferente de la sobrecarga especificada por el usuario, pero aparece en la lista ya que se pasa por alto fácilmente. Por ejemplo, puede agregar a int s y double s usando la misma notación x += 2 y el compilador produce:

  • instrucciones de CPU específicas del tipo
  • un resultado del mismo tipo.

La sobrecarga se extiende a los tipos definidos por el usuario:

std::string x; int y = 0; x += ''c''; y += ''c'';

Las sobrecargas proporcionadas por el compilador para los tipos básicos son comunes en los lenguajes informáticos de alto nivel (3GL +), y la discusión explícita del polimorfismo generalmente implica algo más. (2GLs - lenguajes ensamblados - a menudo requieren que el programador use explícitamente diferentes mnemónicos para diferentes tipos).

> Conversiones estándar

La cuarta sección del Estándar de C ++ describe las conversiones estándar.

El primer punto se resume muy bien (a partir de un borrador anterior, con suerte, sustancialmente correcto):

-1- Las conversiones estándar son conversiones implícitas definidas para tipos incorporados. Clause conv enumera el conjunto completo de dichas conversiones. Una secuencia de conversión estándar es una secuencia de conversiones estándar en el siguiente orden:

  • Cero o una conversión del siguiente conjunto: conversión de lvalue a rvalue, conversión de matriz a puntero y conversión de función a puntero.

  • Cero o una conversión del siguiente conjunto: promociones integrales, promoción de punto flotante, conversiones integrales, conversiones de coma flotante, conversiones de integral flotante, conversiones de puntero, conversiones de puntero a miembro y conversiones booleanas.

  • Cero o una conversión de calificación.

[Nota: una secuencia de conversión estándar puede estar vacía, es decir, puede consistir en ninguna conversión. ] Se aplicará una secuencia de conversión estándar a una expresión si es necesario para convertirla a un tipo de destino requerido.

Estas conversiones permiten códigos tales como:

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

Aplicando la prueba anterior:

Para ser polimórfico, [ a() ] debe ser capaz de operar con valores de al menos dos tipos distintos (por ejemplo, int y double ), encontrar y ejecutar un código apropiado para el tipo .

a() sí mismo ejecuta el código específicamente para el double y, por lo tanto, no es polimórfico.

Pero, en la segunda llamada a a() el compilador sabe generar el código apropiado para el tipo para una "promoción de punto flotante" (Estándar §4) para convertir 42 a 42.0 . Ese código adicional está en la función de llamada . Discutiremos el significado de esto en la conclusión.

> Coerción, moldes, constructores implícitos

Estos mecanismos permiten que las clases definidas por el usuario especifiquen comportamientos similares a las conversiones estándar de los tipos incorporados. Echemos un vistazo:

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

Aquí, el objeto std::cin se evalúa en un contexto booleano, con la ayuda de un operador de conversión. Esto se puede agrupar conceptualmente con "promociones integrales" y otras de las conversiones estándar en el tema anterior.

Los constructores implícitos efectivamente hacen lo mismo, pero están controlados por el tipo cast-to:

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

Implicaciones de sobrecargas, conversiones y coacciones proporcionadas por el compilador

Considerar:

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

Si queremos que la cantidad x se trate como un número real durante la división (es decir, sea 6.5 en lugar de redondearse a 6), solo necesitamos cambiar a typedef double Amount .

Eso está bien, pero no habría sido demasiado trabajo hacer que el código explícitamente "escriba correcto":

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

Pero, considere que podemos transformar la primera versión en una template :

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

Es debido a esas pequeñas "funciones de conveniencia" que puede ser fácilmente instanciado para int o double y funciona como se esperaba. Sin estas características, necesitaríamos conversiones explícitas, rasgos de tipo y / o clases de políticas, algunos desordenados y propensos a errores como:

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

Por lo tanto, la sobrecarga del operador proporcionada por el compilador para los tipos integrados, las conversiones estándar, el colado / la coerción / los constructores implícitos, todos contribuyen de forma sutil al polimorfismo. De la definición en la parte superior de esta respuesta, abordan "encontrar y ejecutar el código apropiado para el tipo" mediante el mapeo:

  • "ausente" de los tipos de parámetros

    • de los muchos tipos de datos controladores de código algorítmico polimórficos

    • codificar escrito para un número (potencialmente menor) de (el mismo u otro) tipo.

  • "a" tipos paramétricos a partir de valores de tipo constante

No establecen contextos polimórficos por sí mismos, pero ayudan a potenciar / simplificar el código dentro de dichos contextos.

Puede sentirse engañado ... no parece mucho. La importancia es que en contextos polimórficos paramétricos (es decir, dentro de plantillas o macros), estamos tratando de admitir una gama arbitrariamente grande de tipos, pero a menudo queremos expresar operaciones sobre ellos en términos de otras funciones, literales y operaciones que fueron diseñadas para una pequeño conjunto de tipos. Reduce la necesidad de crear funciones o datos casi idénticos por tipo cuando la operación / valor es lógicamente el mismo. Estas características cooperan para agregar una actitud de "mejor esfuerzo", haciendo lo que se espera intuitivamente al usar las funciones y datos disponibles y limitados, y solo parando con un error cuando existe una ambigüedad real.

Esto ayuda a limitar la necesidad de código polimórfico que admita código polimórfico, dibujando una red más estrecha alrededor del uso del polimorfismo, por lo que el uso localizado no obliga al uso generalizado y hace que los beneficios del polimorfismo estén disponibles según sea necesario sin imponer el costo de tener que exponer la implementación a tiempo de compilación, tener múltiples copias de la misma función lógica en el código objeto para soportar los tipos usados, y al hacer despacho virtual en oposición a la inlining o al menos llamadas resueltas en tiempo de compilación. Como es típico en C ++, el programador tiene mucha libertad para controlar los límites dentro de los cuales se usa el polimorfismo.

HASTA DONDE SE:

C ++ proporciona tres tipos diferentes de polimorfismo.

  • Funciones virtuales
  • Sobrecarga de nombre de función
  • Sobrecarga del operador

Además de los tres tipos anteriores de polimorfismo, existen otros tipos de polimorfismo:

  • tiempo de ejecución
  • tiempo de compilación
  • polimorfismo ad-hoc
  • polimorfismo paramétrico

Sé que el polimorfismo en tiempo de ejecución se puede lograr mediante funciones virtuales y el polimorfismo estático se puede lograr mediante funciones de plantilla

Pero para los otros dos

  • polimorfismo ad-hoc
  • polimorfismo paramétrico dice el sitio web ,

polimorfismo ad-hoc:

Si el rango de tipos reales que pueden usarse es finito y las combinaciones deben especificarse individualmente antes de su uso, esto se denomina polimorfismo ad-hoc.

polimorfismo paramétrico:

Si todo el código se escribe sin mencionar ningún tipo específico y, por lo tanto, se puede usar de forma transparente con cualquier cantidad de tipos nuevos, se llama polimorfismo paramétrico.

Apenas puedo entenderlos :(

¿Alguien puede explicar ambos si es posible con un ejemplo? Espero que las respuestas a estas preguntas sean útiles para muchas nuevas salidas de sus universidades.


Aquí hay un ejemplo básico usando clases polimórficas

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


En C ++, la distinción importante es el tiempo de ejecución frente al enlace en tiempo de compilación. Ad-hoc vs. paramétrico realmente no ayuda, como explicaré más adelante.

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

Nota: el polimorfismo de tiempo de ejecución aún puede resolverse en tiempo de compilación, pero eso es solo optimización. La necesidad de respaldar la resolución en tiempo de ejecución de manera eficiente, y el intercambio frente a otros problemas, es parte de lo que llevó a las funciones virtuales a ser lo que son. Y eso es realmente clave para todas las formas de polimorfismo en C ++: cada una surge de diferentes conjuntos de concesiones hechas en un contexto diferente.

La sobrecarga de funciones y la sobrecarga del operador son lo mismo en todo lo que importa. Los nombres y la sintaxis para usarlos no afectan el polimorfismo.

Las plantillas le permiten especificar muchas sobrecargas de funciones a la vez.

Hay otro conjunto de nombres para la misma idea de tiempo de resolución ...

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

Estos nombres están más asociados con OOP, por lo que es un poco extraño decir que una plantilla u otra función que no sea miembro utiliza el enlace anticipado.

Para comprender mejor la relación entre las funciones virtuales y la sobrecarga de funciones, también es útil comprender la diferencia entre "envío único" y "envío múltiple". La idea puede ser entendida como una progresión ...

  • Primero, hay funciones monomórficas. La implementación de la función se identifica de manera única por el nombre de la función. Ninguno de los parámetros es especial.
  • Entonces, hay un solo despacho. Uno de los parámetros se considera especial y se usa (junto con el nombre) para identificar qué implementación usar. En OOP, tendemos a pensar en este parámetro como "el objeto", listarlo antes del nombre de la función, etc.
  • Entonces, hay despacho múltiple. Cualquiera / todos los parámetros contribuyen a identificar qué implementación usar. Por lo tanto, una vez más, ninguno de los parámetros debe ser especial.

Obviamente, hay más en OOP que una excusa para designar un parámetro como especial, pero esa es una parte de ello. Y relacionándome con lo que dije acerca de las compensaciones: el envío único es bastante fácil de hacer de manera eficiente (la implementación habitual se llama "tablas virtuales"). El envío múltiple es más incómodo, no solo en términos de eficiencia, sino también para una compilación separada. Si tiene curiosidad, puede buscar "el problema de la expresión".

Así como es un poco extraño usar el término "vinculación anticipada" para las funciones que no son miembros, es un poco extraño usar los términos "envío único" y "despacho múltiple" donde el polimorfismo se resuelve en tiempo de compilación. Por lo general, se considera que C ++ no tiene despacho múltiple, que se considera un tipo particular de resolución en tiempo de ejecución. Sin embargo, la sobrecarga de funciones puede verse como un despacho múltiple hecho en tiempo de compilación.

Volviendo al polimorfismo paramétrico frente al ad hoc, estos términos son más populares en la programación funcional, y no funcionan del todo en C ++. Aún así...

El polimorfismo paramétrico significa que tiene tipos como parámetros, y el mismo código se usa independientemente del tipo que use para esos parámetros.

El polimorfismo ad-hoc es ad-hoc en el sentido de que usted proporciona un código diferente dependiendo de los tipos particulares.

La sobrecarga y las funciones virtuales son ejemplos de polimorfismo ad-hoc.

De nuevo, hay algunos sinónimos ...

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

Excepto que estos no son sinónimos, aunque comúnmente se tratan como si lo fueran, y es allí donde es probable que surja confusión en C ++.

El razonamiento detrás de tratar estos como sinónimos es que al restringir el polimorfismo a clases particulares de tipos, es posible usar operaciones específicas para esas clases de tipos. La palabra "clases" aquí se puede interpretar en el sentido de OOP, pero en realidad solo se refiere a conjuntos (generalmente nombrados) de tipos que comparten ciertas operaciones.

Por lo tanto, el polimorfismo paramétrico generalmente se toma (al menos por defecto) para implicar un polimorfismo no restringido. Debido a que se usa el mismo código independientemente de los parámetros de tipo, las únicas operaciones soportables son aquellas que funcionan para todos los tipos. Al dejar el conjunto de tipos sin restricciones, limita severamente el conjunto de operaciones que puede aplicar a esos tipos.

En eg Haskell, puedes tener ...

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

El aquí es un tipo polimórfico no restringido. Podría ser cualquier cosa, así que no hay mucho que podamos hacer con valores de ese tipo.

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

Aquí, a está obligado a ser miembro de la clase Num , tipos que actúan como números. Esa restricción le permite hacer cosas numéricas con esos valores, como agregarlos. Incluso el 3 es una inferencia de tipo polimórfico, se da cuenta de que te refieres al 3 de tipo a .

Pienso en esto como un polimorfismo paramétrico restringido. Solo hay una implementación, pero solo se puede aplicar en casos limitados. El aspecto ad-hoc es la elección de qué + y 3 usar. Cada "instancia" de Num tiene su propia implementación distinta de estos. Así que incluso en Haskell "paramétrico" y "no restringido" no son sinónimos, ¡no me culpes, no es mi culpa!

En C ++, tanto la sobrecarga como las funciones virtuales son polimorfismos ad-hoc. La definición de polimorfismo ad-hoc no importa si la implementación se selecciona en tiempo de ejecución o en tiempo de compilación.

C ++ se acerca mucho al polimorfismo paramétrico con plantillas si cada parámetro de plantilla tiene el tipo typename . Hay parámetros de tipo, y hay una sola implementación sin importar qué tipos se usan. Sin embargo, la regla "La falla de sustitución no es un error" significa que surgen restricciones implícitas como resultado del uso de operaciones dentro de la plantilla. Las complicaciones adicionales incluyen la especialización de plantilla para proporcionar plantillas alternativas: implementaciones diferentes (ad-hoc).

De modo que C ++ tiene polimorfismo paramétrico, pero está implícitamente restringido y podría ser anulado por alternativas ad-hoc, es decir, esta clasificación realmente no funciona para C ++.



Esto puede no ser de ayuda, pero hice esto para presentarles a mis amigos la programación dando funciones definidas, como START , y END para la función principal, así que no fue demasiado desalentador (solo usaron el archivo main.cpp ). Contiene clases y estructuras polimórficas, plantillas, vectores, matrices, directivas de preprocesador, amistad, operadores y punteros (todos los cuales probablemente deberías saber antes de intentar el polimorfismo):

Nota: no está terminado, pero puede hacerse una idea

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


Polimorfismo significa muchas formas como tal, se usa para que un operador actúe de manera diferente bajo diferentes instancias. El polimorfismo se usa para implementar la herencia. Por ejemplo, hemos definido un dibujo fn () para una forma de clase y luego el dibujo fn se puede implementar para dibujar círculos, recuadros, triángulos y otras formas. (que son objetos de la forma de clase)


Si alguien dice CORTAR a estas personas

The Surgeon The Hair Stylist The Actor

¿Lo que sucederá?

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.

So above representation shows What is polymorphism (same name, different behavior) in OOP.

If you are going for an interview and interviewer asks you tell/show a live example for polymorphism in the same room we are sitting at, say-

Answer - Door / Windows

Wondering How?

Through Door / Window - a person can come, air can come, light can come, rain can come, etc.

ie One form different behavior(Polymorphism).

To understand it better and in a simple manner I used above example.. If you need reference for code follow above answers.