Polimorfismo en C ++


129

HASTA DONDE SE:

C ++ proporciona tres tipos diferentes de polimorfismo.

  • Funciones virtuales
  • Nombre de función sobrecarga
  • 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 de 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 se pueden usar es finito y las combinaciones deben especificarse individualmente antes de su uso, esto se llama 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 número de nuevos tipos, 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 muchos pasajes nuevos de sus universidades.


30
En realidad, C ++ tiene cuatro tipos de polimorfismo: paramétrico (genérico mediante plantillas en C ++), inclusión (subtipificación mediante métodos virtuales en C ++), sobrecarga y coerción (conversiones implícitas). Desde el punto de vista conceptual, hay poca distinción entre la sobrecarga de funciones y la sobrecarga del operador.
fredoverflow

Entonces parece que el sitio web que mencioné es engañoso ... ¿estoy en lo cierto?
Vijay

@zombie: ese sitio web toca muchos conceptos buenos, pero no es preciso y consistente en el uso de la terminología (por ejemplo, una vez que comienza a hablar sobre el polimorfismo de despacho virtual / tiempo de ejecución, hace muchas declaraciones sobre polimorfismo que son incorrectas) en general pero cierto para despacho virtual). Si ya comprende el tema, puede relacionarse con lo que se está diciendo e insertar mentalmente las advertencias necesarias, pero es difícil llegar leyendo el sitio ...
Tony Delroy

Algunos términos son casi sinónimos o están más relacionados pero más restringidos que otros. Por ejemplo, el término "polimorfismo ad-hoc" se usa principalmente en Haskell en mi experiencia, sin embargo, "funciones virtuales" está muy estrechamente relacionado. La pequeña diferencia es que "funciones virtuales" es un término orientado a objetos que se refiere a funciones miembro con "enlace tardío". El "despacho múltiple" también es una especie de polimorfismo ad-hoc. Y como dice FredOverflow, tanto el operador como la sobrecarga de funciones son básicamente lo mismo.
Steve314

Arreglé tu formato para ti. Lea la ayuda disponible a la derecha del panel de edición. Alguien con> 200 preguntas y> 3k debería saber estas cosas básicas. Además, es posible que desee comprar un nuevo teclado. La tecla Mayús de este parece estar fallando intermitentemente. Ah, y: no existe una "función de plantilla" en C ++. Sin embargo, hay plantillas de funciones .
sbi

Respuestas:


219

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 comenzar con una prueba simple y su definición. Considerar:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Aquí, f() es realizar alguna operación y se le están dando valores xy ycomo entradas.

Para polimorfismo exposición, f()debe ser capaz de funcionar con valores de al menos dos distinto tipos (por ejemplo, inty double), encontrando y ejecutando un código distinto apropiado para el tipo.


Mecanismos C ++ para polimorfismo

Polimorfismo explícito especificado por el programador

Puedes 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 la conversión / coerción se analizan más adelante para completarlo como:

  • son comúnmente entendidos intuitivamente de todos modos (garantizando un " reacción de oh, esa "),
  • afectan el umbral al requerir y la fluidez en el uso de los mecanismos anteriores, y
  • La explicación es una distracción incómoda de conceptos más importantes.

Terminología

Categorización adicional

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

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

    • El tiempo de ejecución significa que el compilador debe generar código para todos los tipos que el programa podría manejar mientras se ejecuta, y en tiempo de ejecución se selecciona el código correcto ( envío 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: digamos un programa solo llamado fanteriormente con intargumentos, dependiendo del mecanismo polimórfico utilizado y de las opciones en línea que el compilador podría evitar generar cualquier código f(double), o el código generado podría desecharse en algún momento de la compilación o vinculación. ( todos los mecanismos anteriores excepto el despacho virtual )

  • ¿Qué tipos son compatibles?

    • Ad-hoc significa que proporciona un código explícito para admitir cada tipo (por ejemplo, sobrecarga, especialización de plantilla); Usted agrega explícitamente soporte "para este" (según el significado ad hoc ), algún otro "esto", y tal vez "eso" también ;-).
    • Es decir, paramétrico , puede intentar usar la función para varios tipos de parámetros sin hacer nada específicamente para habilitar su compatibilidad (por ejemplo, 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 irrelevante. Los "conceptos" introducidos por C ++ 20 expresan y hacen cumplir tales expectativas. Consulte la página de preferencias aquí .

      • El polimorfismo paramétrico proporciona la tipificación de patos , un concepto atribuido a James Whitcomb Riley, quien aparentemente dijo: "Cuando veo un pájaro que camina como un pato y nada como un pato y grazna como un pato, llamo a ese pájaro un 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 nuevos tipos 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, puede codificar que cuando el tipo de datos que está procesando tiene un .size()miembro, usará una función, de lo contrario, otra función que no necesita .size()(pero presumiblemente sufre de alguna manera, por ejemplo, usar la strlen()impresión más lenta o no útil un mensaje en el registro). También puede especificar comportamientos ad-hoc cuando la plantilla se instancia con parámetros específicos, ya sea dejando algunos parámetros paramétricos ( especialización parcial de plantilla ) o no ( especialización completa ).

"Polimórfico"

Alf Steinbach comenta que en el polimórfico estándar de C ++ solo se refiere al polimorfismo en tiempo de ejecución mediante el envío virtual. General Comp. 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 a entidades de diferentes tipos. Las funciones virtuales proporcionan polimorfismo dinámico (tiempo de ejecución) a través de una interfaz proporcionada por una clase base. Las funciones y plantillas sobrecargadas proporcionan polimorfismo estático (tiempo de compilación). TC ++ PL 12.2.6, 13.6.1, D&E 2.9.

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

Discusión

Con el estándar C ++ que utiliza una definición más estrecha de "polimorfismo" que el Comp. Sci. comunidad, para garantizar la comprensión mutua de su audiencia, considere ...

  • usando una terminología inequívoca ("¿podemos hacer que este código sea reutilizable para otros tipos?" o "¿podemos usar el envío 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 lo que el polimorfismo realmente está haciendo por usted ...

    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 satisfacen 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 de objetos heterogéneos manejada a través de Base*s,
  • implementación elegida en tiempo de ejecución basada en archivos de configuración, modificadores de línea de comandos, configuraciones de IU, etc.
  • la implementación varió en 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 tiempo de ejecución
  • SFINAE
  • CRTP
  • optimizaciones (muchas de ellas incluyen eliminación de código inactivo y en línea, desenrollado de bucles, matrices basadas en pila estática frente a montón)
  • __FILE__, __LINE__concatenación literal de cadenas y otras capacidades únicas de macros (que siguen siendo malvadas ;-))
  • Se admite el uso semántico de las plantillas y macros de prueba, pero no restrinja artificialmente cómo se proporciona ese soporte (ya que el despacho virtual tiende a requerir que se anulen las funciones de los miembros que coinciden exactamente)

Otros mecanismos que apoyan el polimorfismo

Según lo prometido, para completar, se cubren varios temas periféricos:

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

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

Mecanismos para mapear operaciones específicas de tipo

> Sobrecargas implícitas proporcionadas por el compilador

Conceptualmente, el compilador sobrecarga muchos operadores para los tipos incorporados. No es conceptualmente diferente de la sobrecarga especificada por el usuario, pero se enumera porque se pasa por alto fácilmente. Por ejemplo, se puede añadir a ints y doubles utilizando la misma notación x += 2y el compilador produce:

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

La sobrecarga se extiende sin problemas 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 de ensamblaje - 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 C ++ describe las conversiones estándar.

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

-1- Las conversiones estándar son conversiones implícitas definidas para los tipos integrados. La cláusula conv enumera el conjunto completo de tales 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 lvalue-to-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 punto flotante, conversiones integrales flotantes, 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, no puede consistir en conversiones. ] 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 como:

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

a(3.14);
a(42);

Aplicando la prueba anterior:

Para ser polimórfico, [ a()] debe poder operar con valores de al menos dos tipos distintos (por ejemplo, inty double), encontrando y ejecutando código apropiado para cada tipo .

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

Sin embargo, en la segunda convocatoria para a()el compilador sabe para generar el código de tipo apropiado para una "promoción de coma flotante" (Norma § 4) para convertir 42a 42.0. Ese código adicional está en la función de llamada . Discutiremos la importancia 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::cinse evalúa en un contexto booleano, con la ayuda de un operador de conversión. Esto se puede agrupar conceptualmente con "promociones integrales" y otros de las conversiones estándar en el tema anterior.

Los constructores implícitos efectivamente hacen lo mismo, pero están controlados por el tipo de conversión:

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

Implicaciones de sobrecargas, conversiones y coerción 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 xsea ​​tratada como un número real durante la división (es decir, ser 6.5 en lugar de redondearse a 6), solo necesitamos cambiar a typedef double Amount.

Eso es bueno, 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 template:

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

Debido a esas pequeñas "características de conveniencia", se puede crear una instancia tan fácil para cualquiera into doublepara trabajar según lo previsto. Sin estas características, necesitaríamos conversiones explícitas, rasgos de tipo y / o clases de políticas, algún desorden detallado y propenso 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 tipos incorporados, conversiones estándar, fundición / coerción / constructores implícitos: todos contribuyen con un sutil soporte para el polimorfismo. Desde la definición en la parte superior de esta respuesta, abordan "encontrar y ejecutar código apropiado para el tipo" mediante el mapeo:

  • "lejos" de los tipos de parámetros

    • de los muchos tipos de datos manejadores de códigos algorítmicos polimórficos

    • al código escrito para un número (potencialmente menor) de (el mismo u otro) tipo.

  • "para" tipos paramétricos de valores de tipo constante

Ellos no establecen contextos polimórficos por sí mismos, pero sí ayudar a potenciar / código de simplificar el interior 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 amplia gama de tipos arbitrariamente, pero a menudo queremos expresar operaciones sobre ellos en términos de otras funciones, literales y operaciones que fueron diseñadas para un 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 intuitivamente se espera mediante el uso de las funciones y los datos disponibles limitados y solo se detienen 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 ajustada alrededor del uso del polimorfismo para que el uso localizado no fuerce el uso generalizado, y haciendo que los beneficios del polimorfismo estén disponibles según sea necesario sin imponer los costos de tener que exponer la implementación en tiempo de compilación, tener múltiples copias de la misma función lógica en el código objeto para admitir los tipos utilizados, y al hacer despacho virtual en lugar de llamadas en línea 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.


1
-1 Gran respuesta a excepción de la discusión terminológica. El estándar C ++ define el término "polimórfico" en §1.8 / 1, refiriéndose a la sección 10.3 sobre funciones virtuales. Por lo tanto, no hay margen de maniobra, no hay espacio para la discusión, no hay espacio para la opinión personal: en el contexto del estándar C ++, ese término se define de una vez por todas. Y juega un papel en la práctica. Por ejemplo, §5.2.7 / 6 aproximadamente dynamic_castrequiere un "puntero o un valor de tipo polimórfico". Saludos y hth.,
Saludos y hth. - Alf

@Alf: gran referencia, aunque creo que su perspectiva es demasiado estrecha. De la pregunta que enumera la sobrecarga, el polimorfismo ad-hoc y paramétrico, etc., queda muy claro que la respuesta debe relacionar las capacidades de C ++ con el Comp general. Sci. significado de los términos. De hecho, el glosario de Stroustrup dice "polimorfismo: proporciona una interfaz única a entidades de diferentes tipos. Las funciones virtuales proporcionan polimorfismo dinámico (tiempo de ejecución) a través de una interfaz proporcionada por una clase base. Las funciones y plantillas sobrecargadas proporcionan polimorfismo estático (tiempo de compilación)". TC ++ PL 12.2.6, 13.6.1, D&E 2.9 ".
Tony Delroy

@ Tony: no es el objetivo principal de su respuesta es incorrecta. Está bien, es genial. es solo que wrt. terminología la entendiste al revés: la terminología académica formal es la estrecha definida por el Estándar Internacional Sagrado, y la terminología aproximada informal donde la gente puede significar cosas ligeramente diferentes, es la que se usa principalmente en esta pregunta y respuesta. Saludos y hth.,
Saludos y hth. - Alf

@Alf: Desearía que la respuesta fuera excelente: "Otros mecanismos" deben reescribirse en una quinta parte de las líneas, y estoy contemplando / redactando características y consecuencias más concretas que contrasten los mecanismos polimórficos. De todos modos, entiendo que el significado académico formal centrado exclusivamente en C ++ puede ser limitado, pero el académico general formal Comp. Sci. el significado no es, como lo demuestra el glosario de Stroustrup. Necesitamos algo definitivo, por ejemplo, definición de Knuth, sin suerte todavía en Google. Aprecio que seas un gurú de C ++, pero ¿puedes señalar evidencia pertinente sobre esto específicamente?
Tony Delroy

1
@Alf: en segundo lugar, estoy seguro de que el polimorfismo se define formalmente en cualquier Comp general decente. Sci. libro de una manera (atemporal, estable) compatible con mi uso (y el de Stroustrup). El artículo de Wikipedia vincula algunas publicaciones académicas que lo definen de esa manera: "Las funciones polimórficas son funciones cuyos operandos (parámetros reales) pueden tener más de un tipo. Los tipos polimórficos son tipos cuyas operaciones son aplicables a valores de más de un tipo". (de lucacardelli.name/Papers/OnUnderstanding.A4.pdf ). Entonces, la pregunta es "¿quién habla por Comp. Sci" ...?
Tony Delroy

15

En C ++, la distinción importante es el enlace en tiempo de ejecución frente al 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 en tiempo de ejecución aún puede resolverse en tiempo de compilación, pero eso es solo optimización. La necesidad de admitir la resolución del tiempo de ejecución de manera eficiente y la compensación contra otros problemas, es parte de lo que llevó a que las funciones virtuales sean lo que son. Y eso es realmente clave para todas las formas de polimorfismo en C ++: cada una surge de diferentes conjuntos de compensaciones realizadas en un contexto diferente.

La sobrecarga de funciones y la sobrecarga del operador son lo mismo en todos los aspectos importantes. 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 resolución-tiempo ...

|---------------+--------------|
| 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 es miembro usa el enlace temprano.

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

  • Primero, hay funciones monomórficas. La implementación de la función se identifica de forma exclusiva por el nombre de la función. Ninguno de los parámetros es especial.
  • Entonces, hay un despacho único. 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", enumerarlo 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 nominar un parámetro como especial, pero esa es una parte de él. Y en relación con lo que dije sobre las compensaciones: el despacho único es bastante fácil de hacer de manera eficiente (la implementación habitual se llama "tablas virtuales"). El despacho múltiple es más incómodo, no solo en términos de eficiencia, sino también para la compilación por separado. Si tiene curiosidad, puede buscar "el problema de expresión".

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

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

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

El polimorfismo ad-hoc es ad-hoc en el sentido de que proporciona un código diferente según 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 ahí 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 utilizar operaciones específicas para esas clases de tipos. La palabra "clases" aquí se puede interpretar en el sentido OOP, pero en realidad solo se refiere a conjuntos (generalmente denominados) de tipos que comparten ciertas operaciones.

Por lo tanto, el polimorfismo paramétrico generalmente se toma (al menos por defecto) para implicar polimorfismo sin restricciones. Debido a que se usa el mismo código independientemente de los parámetros de tipo, las únicas operaciones compatibles 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, por ejemplo, Haskell, puedes tener ...

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

El aaquí es un tipo polimórfico sin restricciones. Podría ser cualquier cosa, por lo que no hay mucho que podamos hacer con valores de ese tipo.

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

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

Pienso en esto como polimorfismo paramétrico restringido. Solo hay una implementación, pero solo se puede aplicar en casos restringidos. El aspecto ad-hoc es la elección de cuál +y 3de usar. Cada "instancia" de Numtiene su propia implementación distinta de estos. Así que incluso en Haskell "paramétrico" y "sin restricciones" no son realmente sinónimos, ¡no me culpen, 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 la plantilla tiene tipo typename. Hay parámetros de tipo, y hay una única implementación sin importar qué tipos se usen. 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 plantillas para proporcionar plantillas alternativas, implementaciones diferentes (ad-hoc).

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


+1 Muchos puntos e ideas interesantes. Solo pasé unas horas leyendo sobre Haskell, así que " aaquí hay un tipo polimórfico [...] sin restricciones, así que no hay mucho que podamos hacer con valores de ese tipo". era de interés: en C ++ sin Conceptos, no está restringido a intentar solo un conjunto específico de operaciones en un argumento de un tipo especificado como parámetro de plantilla ... las bibliotecas como los conceptos de impulso funcionan de otra manera, asegurándose de que el tipo sea compatible con las operaciones usted especifica, en lugar de evitar el uso accidental de operaciones adicionales.
Tony Delroy

@ Tony - Los conceptos son una forma de restringir explícitamente el polimorfismo de las plantillas. Las restricciones implícitas obviamente no desaparecerán debido a la compatibilidad, pero las restricciones explícitas definitivamente mejorarán las cosas significativamente. Estoy bastante seguro de que algunos planes anteriores de conceptos estaban algo relacionados con las clases de tipos de Haskell, aunque no los examiné tan profundamente y la última vez que miré "superficialmente" no conocía mucho a Haskell.
Steve314

"Las restricciones implícitas obviamente no desaparecerán debido a la compatibilidad" - desde la memoria, C ++ 0x Concepts sí (prometió: - /) evitar "restricciones implícitas" - solo se podía usar el tipo de la manera prometida por los Conceptos.
Tony Delroy

2

En cuanto al polimorfismo ad-hoc, significa sobrecarga de funciones o sobrecarga del operador. Mira aquí:

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

En cuanto al polimorfismo paramétrico, las funciones de plantilla también se pueden contar porque no necesariamente incluyen parámetros de tipos FIJOS. Por ejemplo, una función puede ordenar una matriz de enteros y también puede ordenar una matriz de cadenas, etc.

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


1
Desafortunadamente, aunque correcto, esto es engañoso. Las funciones de la plantilla pueden obtener restricciones implícitas debido a la regla SFINAE (el uso de una operación dentro de la plantilla restringe implícitamente el polimorfismo) y la especialización de la plantilla puede proporcionar plantillas alternativas ad-hoc que anulan las plantillas más generales. Por lo tanto, una plantilla (de forma predeterminada) proporciona un polimorfismo paramétrico sin restricciones, pero no hay aplicación de eso: hay al menos dos formas en que puede volverse restringido o ad-hoc.
Steve314

De hecho, su ejemplo, la clasificación, implica una restricción. La ordenación solo funciona para los tipos que están ordenados (es decir, proporcionan <operadores similares y similares). En Haskell, expresarías ese requisito explícitamente usando la clase Ord. El hecho de que obtenga un diferente <según el tipo particular (según lo suministrado por la instancia de Ord) se consideraría polimorfismo ad-hoc.
Steve314

2

Esto puede no ser de ninguna ayuda, pero hice esto para presentarles a mis amigos la programación al darles funciones definidas, como START, y ENDpara la función principal, por lo 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 (todo lo cual probablemente debería saber antes de intentar el polimorfismo):

Nota: no está terminado, pero puedes hacerte 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;
            }
        }
    }
};

1

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

0

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


-3

Si alguien dice CORTE 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.

Entonces, la representación anterior muestra qué es el polimorfismo (mismo nombre, comportamiento diferente) en OOP.

Si vas a una entrevista y el entrevistador te pide que digas / muestres un ejemplo en vivo de polimorfismo en la misma habitación en la que estamos sentados, di-

Respuesta: puerta / ventanas

¿Se pregunta cómo?

A través de la puerta / ventana: puede venir una persona, puede llegar el aire, puede venir la luz, puede venir la lluvia, etc.

es decir, una forma de comportamiento diferente (polimorfismo).

Para entenderlo mejor y de manera simple, utilicé el ejemplo anterior. Si necesita una referencia para el código, siga las respuestas anteriores.


Como mencioné para una mejor comprensión del polimorfismo en c ++, utilicé el ejemplo anterior. Esto podría ayudar a una persona más fresca a comprender y relatar realmente cuál es el significado o lo que está sucediendo detrás del código durante la entrevista. ¡Gracias!
Sanchit

op preguntó "polimorfismo en c ++". Tu respuesta es demasiado abstracta.
StahlRat
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.