Polimorfismo em C ++

129

ATÉ ONDE SEI:

O C ++ fornece três tipos diferentes de polimorfismo.

  • Funções virtuais
  • Sobrecarga de nome de função
  • Sobrecarga do operador

Além dos três tipos de polimorfismo acima, existem outros tipos de polimorfismo:

  • tempo de execução
  • tempo de compilação
  • polimorfismo ad-hoc
  • polimorfismo paramétrico

Eu sei que o polimorfismo de tempo de execução pode ser alcançado por funções virtuais e o polimorfismo estático pode ser alcançado por funções de modelo

Mas para os outros dois

  • polimorfismo ad-hoc
  • polimorfismo paramétrico que o site diz ,

polimorfismo ad-hoc:

Se o intervalo de tipos reais que pode ser usado é finito e as combinações devem ser especificadas individualmente antes do uso, isso é chamado de polimorfismo ad-hoc.

polimorfismo paramétrico:

Se todo o código é escrito sem menção de qualquer tipo específico e, portanto, pode ser usado de forma transparente com qualquer número de novos tipos, é chamado de polimorfismo paramétrico.

Eu mal consigo entendê-los :(

alguém pode explicar os dois, se possível, com um exemplo? Espero que as respostas a essas perguntas sejam úteis para muitos novos moradores de suas faculdades.

Vijay
fonte
30
Na verdade, o C ++ possui quatro tipos de polimorfismo: paramétrico (genericidade por modelos em C ++), inclusão (subtipagem por métodos virtuais em C ++), sobrecarga e coerção (conversões implícitas). Conceitualmente, há pouca distinção entre sobrecarga de função e sobrecarga de operador.
Fredoverflow
Então parece que o site que mencionei é enganoso para muitos ... estou correto?
Vijay
@zombie: esse site aborda muitos bons conceitos, mas não é preciso e consistente no uso da terminologia (por exemplo, quando começa a falar sobre polimorfismo de despacho / tempo de execução virtual, ele faz muitas declarações sobre polimorfismos incorretos em geral, mas verdadeiro para despacho virtual). Se você já entende do assunto, você pode se relacionar com o que está sendo dito e mentalmente inserir as advertências necessárias, mas é difícil para chegar lá lendo o site ....
Tony Delroy
Alguns termos são quase sinônimos, ou mais relacionados a, mas mais restritos que outros termos. Por exemplo, o termo "polimorfismo ad-hoc" é usado principalmente em Haskell na minha experiência, mas "funções virtuais" está intimamente relacionada. A menor diferença é que "funções virtuais" é um termo orientado a objetos que se refere a funções-membro com "ligação tardia". O "despacho múltiplo" também é um tipo de polimorfismo ad-hoc. E, como diz o FredOverflow, a sobrecarga de operadores e funções é basicamente a mesma coisa.
21311 Steve314
Corrigi sua formatação para você. Por favor, leia a ajuda disponível à direita do painel de edição. Alguém com> 200 perguntas e> 3k deve conhecer esse material básico. Além disso, você pode querer comprar um novo teclado. A tecla shift dessa pessoa parece estar falhando intermitentemente. Ah, e: não existe uma "função de modelo" em C ++. Existem, no entanto, modelos de função .
Sbi

Respostas:

219

Compreensão / requisitos para o polimorfismo

Para entender o polimorfismo - como o termo é usado na Ciência da Computação -, ajuda a começar com um teste simples e sua definição. Considerar:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Aqui, f()é realizar alguma operação e receber valores xe ycomo entradas.

Para exibir polimorfismo, f()deve ser capaz de operar com valores de pelo menos dois tipos distintos (por exemplo, inte double), localizando e executando códigos distintos de tipo apropriado.


Mecanismos C ++ para polimorfismo

Polimorfismo explícito especificado pelo programador

Você pode escrever de f()forma que ele possa operar em vários tipos de qualquer uma das seguintes maneiras:

  • Pré-processando:

    #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; }
    
  • Modelos:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • Expedição 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
    

Outros mecanismos relacionados

O polimorfismo fornecido pelo compilador para tipos incorporados, conversões padrão e conversão / coerção são discutidos posteriormente para fins de completude como:

  • eles são geralmente intuitivamente entendidos de qualquer maneira (justificando uma reação " oh, essa "),
  • eles impactam o limiar ao exigir e uniformidade no uso dos mecanismos acima, e
  • explicação é uma distração minuciosa de conceitos mais importantes.

Terminologia

Categorização adicional

Dados os mecanismos polimórficos acima, podemos categorizá-los de várias maneiras:

  • Quando o código específico do tipo polimórfico é selecionado?

    • Tempo de execução significa que o compilador deve gerar código para todos os tipos que o programa pode manipular durante a execução e, em tempo de execução, o código correto é selecionado ( despacho virtual )
    • Tempo de compilação significa que a escolha do código específico do tipo é feita durante a compilação. Uma conseqüência disso: digamos que um programa é chamado apenas facima com intargumentos - dependendo do mecanismo polimórfico usado e das opções embutidas para as quais o compilador pode evitar gerar qualquer código f(double), ou o código gerado pode ser jogado fora em algum momento da compilação ou vinculação. ( todos os mecanismos acima, exceto despacho virtual )

  • Quais tipos são suportados?

    • Ad-hoc significa que você fornece código explícito para suportar cada tipo (por exemplo, sobrecarga, especialização de modelo); você adiciona explicitamente suporte "para este" (conforme o significado ad hoc ), algum outro "isto" e talvez "isso" também ;-).
    • Significado paramétrico, você pode apenas tentar usar a função para vários tipos de parâmetros sem fazer nada especificamente para permitir seu suporte a eles (por exemplo, modelos, macros). Um objeto com funções / operadores que agem como o modelo / macro espera 1 é tudo o que o modelo / macro precisa para fazer seu trabalho, sendo o tipo exato irrelevante. Os "conceitos" introduzidos pelo C ++ 20 expressam e reforçam essas expectativas - consulte a página cppreference aqui .

      • O polimorfismo paramétrico fornece tipagem de pato - um conceito atribuído a James Whitcomb Riley, que aparentemente disse: "Quando vejo um pássaro que anda como um pato e nada como um pato e grasna como um pato, eu chamo esse pássaro de pato". .

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • O polimorfismo do subtipo (também conhecido como inclusão) permite trabalhar em novos tipos sem atualizar o algoritmo / função, mas eles devem ser derivados da mesma classe base (despacho virtual)

1 - Os modelos são extremamente flexíveis. O SFINAE (veja também std::enable_if) permite efetivamente vários conjuntos de expectativas para o polimorfismo paramétrico. Por exemplo, você pode codificar que, quando o tipo de dados que você está processando tiver um .size()membro, você usará uma função, caso contrário, outra função que não precisa .size()(mas provavelmente sofre de alguma forma - por exemplo, usar o mais lento strlen()ou não imprimir como útil uma mensagem no log). Você também pode especificar comportamentos ad-hoc quando o modelo é instanciado com parâmetros específicos, deixando alguns parâmetros paramétricos ( especialização parcial do modelo ) ou não ( especialização completa ).

"Polimórfico"

Alf Steinbach comenta que no padrão C ++ polimórfico refere-se apenas ao polimorfismo em tempo de execução usando despacho virtual. General Comp. Sci. o significado é mais abrangente, conforme o glossário do criador de C ++, Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):

polimorfismo - fornecendo uma interface única para entidades de diferentes tipos. As funções virtuais fornecem polimorfismo dinâmico (tempo de execução) por meio de uma interface fornecida por uma classe base. Funções e modelos sobrecarregados fornecem polimorfismo estático (em tempo de compilação). TC ++ PL 12.2.6, 13.6.1, D&E 2.9.

Essa resposta - como a pergunta - relaciona os recursos do C ++ ao Comp. Sci. terminologia.

Discussão

Com o padrão C ++ usando uma definição mais restrita de "polimorfismo" que o Comp. Sci. comunidade, para garantir um entendimento mútuo para o seu público, considere ...

  • usando terminologia inequívoca ("podemos tornar esse código reutilizável para outros tipos?" ou "podemos usar o despacho virtual?" em vez de "podemos tornar esse código polimórfico?") e / ou
  • definindo claramente sua terminologia.

Ainda assim, o que é crucial para ser um ótimo programador de C ++ é entender o que o polimorfismo realmente está fazendo por você ...

    permitindo escrever código "algorítmico" uma vez e depois aplicá-lo a muitos tipos de dados

... e esteja ciente de como os diferentes mecanismos polimórficos correspondem às suas necessidades reais.

O polimorfismo em tempo de execução é adequado:

  • entrada processada por métodos de fábrica e cuspida como uma coleção de objetos heterogênea manipulada via Base*s,
  • implementação escolhida em tempo de execução com base em arquivos de configuração, opções de linha de comando, configurações de interface do usuário etc.,
  • a implementação variou em tempo de execução, como para um padrão de máquina de estado.

Quando não há um driver claro para o polimorfismo em tempo de execução, as opções em tempo de compilação geralmente são preferíveis. Considerar:

  • o aspecto de compilar o que é chamado de classes modeladas é preferível a interfaces gordas que falham no tempo de execução
  • SFINAE
  • CRTP
  • otimizações (muitas incluindo eliminação de inline e de código morto, desenrolamento de loop, matrizes estáticas baseadas em pilha e heap)
  • __FILE__, __LINE__concatenação literal de cadeias de caracteres e outros recursos exclusivos das macros (que permanecem ruins ;-))
  • modelos e macros testam o uso semântico, mas não restringem artificialmente como esse suporte é fornecido (como o despacho virtual costuma exigir, substituindo exatamente as substituições da função de membro)

Outros mecanismos de apoio ao polimorfismo

Como prometido, para completar, vários tópicos periféricos são abordados:

  • sobrecargas fornecidas pelo compilador
  • conversões
  • lança / coerção

Esta resposta termina com uma discussão de como as opções acima se combinam para capacitar e simplificar o código polimórfico - especialmente o polimorfismo paramétrico (modelos e macros).

Mecanismos de mapeamento para operações específicas de tipo

> Sobrecargas implícitas fornecidas pelo compilador

Conceitualmente, o compilador sobrecarrega muitos operadores para tipos internos. Não é conceitualmente diferente da sobrecarga especificada pelo usuário, mas é listada, pois é facilmente ignorada. Por exemplo, você pode adicionar ao ints e doubles usando a mesma notação x += 2eo compilador produz:

  • instruções de CPU específicas do tipo
  • um resultado do mesmo tipo.

A sobrecarga se estende sem problemas para tipos definidos pelo usuário:

std::string x;
int y = 0;

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

Sobrecargas fornecidas pelo compilador para tipos básicos são comuns em linguagens de computador de alto nível (3GL +), e a discussão explícita do polimorfismo geralmente implica algo mais. (2GLs - linguagens assembly - geralmente exigem que o programador use explicitamente diferentes mnemônicos para diferentes tipos.)

> Conversões padrão

A quarta seção do padrão C ++ descreve as conversões padrão.

O primeiro ponto resume bem (de um rascunho antigo - espero que ainda esteja substancialmente correto):

-1- Conversões padrão são conversões implícitas definidas para tipos internos. A cláusula conv enumera o conjunto completo dessas conversões. Uma sequência de conversão padrão é uma sequência de conversões padrão na seguinte ordem:

  • Conversão zero ou uma do conjunto a seguir: conversão lvalue para rvalue, conversão de matriz em ponteiro e conversão de função em ponteiro.

  • Conversão zero ou uma do conjunto a seguir: promoções integrais, promoção de ponto flutuante, conversões integrais, conversões de ponto flutuante, conversões integrais flutuantes, conversões de ponteiro, conversões de ponteiro, conversões de ponteiro para membro e conversões booleanas.

  • Zero ou uma conversão de qualificação.

[Nota: uma sequência de conversão padrão pode estar vazia, ou seja, não pode conter conversões. ] Uma sequência de conversão padrão será aplicada a uma expressão, se necessário, para convertê-la em um tipo de destino necessário.

Essas conversões permitem códigos como:

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

a(3.14);
a(42);

Aplicando o teste anterior:

Para ser polimórfico, [ a()] deve ser capaz de operar com valores de pelo menos dois tipos distintos (por exemplo, inte double), localizando e executando o código apropriado ao tipo .

a()ele próprio executa código especificamente para doublee, portanto, não é polimórfico.

Mas, na segunda chamada para a()o compilador sabe para gerar código de tipo apropriado para uma "promoção de ponto flutuante" (Standard §4) para converter 42a 42.0. Esse código extra está na função de chamada . Discutiremos o significado disso na conclusão.

> Coerção, elencos, construtores implícitos

Esses mecanismos permitem que as classes definidas pelo usuário especifiquem comportamentos semelhantes às conversões padrão dos tipos internos. Vamos dar uma olhada:

int a, b;

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

Aqui, o objeto std::ciné avaliado em um contexto booleano, com a ajuda de um operador de conversão. Isso pode ser agrupado conceitualmente com "promoções integrais" e outras das conversões padrão no tópico acima.

Construtores implícitos efetivamente fazem a mesma coisa, mas são controlados pelo tipo de conversão:

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

Implicações de sobrecargas, conversões e coerção fornecidas pelo compilador

Considerar:

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

Se queremos que a quantia xseja tratada como um número real durante a divisão (ou seja, 6,5 em vez de arredondada para 6), precisamos apenas mudar para typedef double Amount.

Isso é bom, mas não teria sido também muito trabalho para tornar o código explicitamente "tipo correto":

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

Mas considere que podemos transformar a primeira versão em template:

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

É por causa desses pequenos "recursos de conveniência" que ele pode ser facilmente instanciado para um intou outro doublee funcionar como pretendido. Sem esses recursos, precisaríamos de elencos explícitos, traços de tipo e / ou classes de política, alguma confusão detalhada e propensa a erros 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;
}

Portanto, sobrecarga de operador fornecida pelo compilador para tipos internos, conversões padrão, fundição / coerção / construtores implícitos - todos eles contribuem com suporte sutil ao polimorfismo. A partir da definição na parte superior desta resposta, eles abordam "localizando e executando código apropriado ao tipo" mapeando:

  • "ausente" dos tipos de parâmetros

    • dos muitos tipos de dados que manipulam códigos algorítmicos polimórficos

    • para o código escrito para um número (potencialmente menos) de (os mesmos ou outros) tipos.

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

Eles não estabelecem contextos polimórficos por si mesmos, mas ajudam a capacitar / simplificar o código dentro de tais contextos.

Você pode se sentir enganado ... não parece muito. O significado é que, em contextos polimórficos paramétricos (ou seja, dentro de modelos ou macros), estamos tentando oferecer suporte a uma variedade arbitrariamente grande de tipos, mas geralmente queremos expressar operações sobre eles em termos de outras funções, literais e operações projetadas para um pequeno conjunto de tipos. Reduz a necessidade de criar funções ou dados quase idênticos por tipo quando a operação / valor é logicamente o mesmo. Esses recursos cooperam para adicionar uma atitude de "melhor esforço", fazendo o que é intuitivamente esperado, usando as funções e os dados disponíveis limitados e parando apenas com um erro quando há ambiguidade real.

Isso ajuda a limitar a necessidade de código polimórfico que suporte código polimórfico, criando uma rede mais rígida em torno do uso de polimorfismo, para que o uso localizado não force o uso generalizado e disponibilizando os benefícios do polimorfismo conforme necessário, sem impor os custos de expor a implementação em tempo de compilação, tenha várias cópias da mesma função lógica no código do objeto para dar suporte aos tipos usados ​​e ao realizar expedição virtual em vez de chamadas internas ou, pelo menos, resolvidas em tempo de compilação. Como é típico em C ++, o programador tem muita liberdade para controlar os limites dentro dos quais o polimorfismo é usado.

Tony Delroy
fonte
1
-1 Ótima resposta, exceto na discussão terminológica. O padrão C ++ define o termo "polimórfico" em §1.8 / 1, referindo-se à seção 10.3 sobre funções virtuais. Portanto, não há espaço de manobra, espaço para discussão, espaço para opinião pessoal: no contexto do C ++ padrão, esse termo é definido de uma vez por todas. E ele desempenha um papel na prática. Por exemplo, §5.2.7 / 6 about dynamic_castrequer um "ponteiro para ou um valor l de um tipo polimórfico". Cheers & hth.,
Cheers e hth. -
@ Alf: ótima referência - embora eu pense que sua perspectiva é muito estreita. É muito claro na pergunta que lista sobrecarga, polimorfismo ad-hoc e paramétrico, etc. que a resposta deve relacionar os recursos do C ++ ao Comp geral. Sci. significado dos termos. De fato, o glossário de Stroustrup diz "polimorfismo - fornecendo uma interface única para entidades de diferentes tipos. Funções virtuais fornecem polimorfismo dinâmico (em tempo de execução) por meio de uma interface fornecida por uma classe base. Funções e modelos sobrecarregados fornecem polimorfismo estático (em tempo de compilação). TC ++ PL 12.2.6, 13.6.1, D&E 2.9. "
Tony Delroy
@ Tony: não é o principal impulso da sua resposta está errada. está tudo bem, está ótimo. é apenas isso errado. terminologia - você entendeu de trás para frente: a terminologia acadêmica formal é a estrita definida pelo Santo Padrão Internacional, e a terminologia aproximada informal em que as pessoas podem significar coisas um pouco diferentes é a usada principalmente nesta pergunta e resposta. Cheers & hth.,
Cheers e hth. #
@ Alf: Eu gostaria que a resposta fosse ótima - "Outros mecanismos" precisam ser reescritos em um quinto das linhas, e estou contemplando / redigindo características e implicações mais concretas, contrastando os mecanismos polimórficos. De qualquer forma, meu entendimento é que o significado acadêmico formal exclusivamente focado em C ++ pode ser estreito, mas o general acadêmico formal Comp. Sci. o significado não é, como evidenciado pelo glossário de Stroustrup. Precisamos de algo definitivo - por exemplo, definição de Knuth - sem sorte ainda no Google. Agradeço que você seja um guru de C ++, mas você pode apontar evidências pertinentes sobre isso especificamente?
Tony Delroy
1
@ Alf: em segundo lugar, estou confiante de que o polimorfismo é formalmente definido em qualquer Comp geral decente. Sci. livro de uma maneira (atemporal, estável) compatível com meu uso (e com o Stroustrup). O artigo da Wikipedia vincula algumas publicações acadêmicas que a definem dessa maneira: "Funções polimórficas são funções cujos operandos (parâmetros reais) podem ter mais de um tipo. Tipos polimórficos são tipos cujas operações são aplicáveis ​​a valores de mais de um tipo". (de lucacardelli.name/Papers/OnUnderstanding.A4.pdf ). Então, a pergunta é "quem fala pelo Comp. Sci" ...?
Tony Delroy
15

No C ++, a distinção importante é a ligação em tempo de execução versus em tempo de compilação. Ad-hoc x paramétrico não ajuda muito, como explicarei mais adiante.

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

Nota - o polimorfismo em tempo de execução ainda pode ser resolvido em tempo de compilação, mas isso é apenas otimização. A necessidade de oferecer suporte à resolução em tempo de execução de maneira eficiente e a negociação com outros problemas faz parte do que levou as funções virtuais a serem o que são. E isso é realmente fundamental para todas as formas de polimorfismo em C ++ - cada uma decorre de diferentes conjuntos de trocas feitas em um contexto diferente.

Sobrecarga de funções e sobrecarga do operador são a mesma coisa em todos os aspectos que importam. Os nomes e a sintaxe para usá-los não afetam o polimorfismo.

Os modelos permitem especificar muitas sobrecargas de função de uma só vez.

Há outro conjunto de nomes para a mesma ideia de tempo de resolução ...

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

Esses nomes estão mais associados ao OOP, portanto, é um pouco estranho dizer que um modelo ou outra função de não-membro usa ligação antecipada.

Para entender melhor o relacionamento entre funções virtuais e sobrecarga de funções, também é útil entender a diferença entre "despacho único" e "despacho múltiplo". A ideia pode ser entendida como uma progressão ...

  • Primeiro, existem funções monomórficas. A implementação da função é identificada exclusivamente pelo nome da função. Nenhum dos parâmetros é especial.
  • Então, há um despacho único. Um dos parâmetros é considerado especial e usado (junto com o nome) para identificar qual implementação usar. Em OOP, tendemos a pensar nesse parâmetro como "o objeto", listá-lo antes do nome da função etc.
  • Então, há vários despachos. Todos / todos os parâmetros contribuem para identificar qual implementação usar. Portanto, mais uma vez, nenhum dos parâmetros precisa ser especial.

Obviamente, há mais no POO do que uma desculpa para nomear um parâmetro como especial, mas essa é uma parte dele. E voltando ao que eu disse sobre compensações - o envio único é bastante fácil de ser feito com eficiência (a implementação usual é chamada de "tabelas virtuais"). O envio múltiplo é mais complicado, não apenas em termos de eficiência, mas também para compilação separada. Se você estiver curioso, pode procurar "o problema da expressão".

Assim como é um pouco estranho usar o termo "ligação antecipada" para funções que não são membros, é um pouco estranho usar os termos "despacho único" e "despacho múltiplo" em que o polimorfismo é resolvido no momento da compilação. Normalmente, considera-se que o C ++ não possui vários despachos, o que é considerado um tipo específico de resolução em tempo de execução. No entanto, a sobrecarga de funções pode ser vista como despacho múltiplo feito em tempo de compilação.

Voltando ao polimorfismo paramétrico x ad-hoc, esses termos são mais populares em programação funcional e não funcionam muito bem em C ++. Mesmo assim...

Polimorfismo paramétrico significa que você tem tipos como parâmetros, e exatamente o mesmo código é usado, independentemente do tipo usado para esses parâmetros.

O polimorfismo ad-hoc é ad-hoc no sentido de que você fornece um código diferente, dependendo dos tipos específicos.

Sobrecarga e funções virtuais são exemplos de polimorfismo ad-hoc.

Novamente, há alguns sinônimos ...

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

Exceto que estes não são sinônimos, embora sejam comumente tratados como se fossem, e é aí que é provável que ocorra confusão no C ++.

O raciocínio por trás de tratá-los como sinônimos é que, restringindo o polimorfismo a classes particulares de tipos, torna-se possível usar operações específicas para essas classes de tipos. A palavra "classes" aqui pode ser interpretada no sentido de POO, mas realmente se refere apenas a conjuntos de tipos (geralmente nomeados) que compartilham determinadas operações.

Portanto, o polimorfismo paramétrico geralmente é usado (pelo menos por padrão) para implicar polimorfismo irrestrito. Como o mesmo código é usado independentemente dos parâmetros de tipo, as únicas operações suportáveis ​​são aquelas que funcionam para todos os tipos. Ao deixar o conjunto de tipos sem restrições, você limita severamente o conjunto de operações que pode aplicar a esses tipos.

Por exemplo, Haskell, você pode ter ...

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

O aaqui é um tipo polimórfico irrestrito. Pode ser qualquer coisa, então não podemos fazer muito com valores desse tipo.

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

Aqui, aé restrito a ser um membro da Numclasse - tipos que agem como números. Essa restrição permite que você faça coisas numéricas com esses valores, como adicioná-los. Até a 3inferência do tipo polimórfica indica que você quer dizer o 3tipo a.

Penso nisso como polimorfismo paramétrico restrito. Há apenas uma implementação, mas ela só pode ser aplicada em casos restritos. O aspecto ad-hoc é a escolha de qual +e 3usar. Cada "instância" de Numpossui sua própria implementação distinta. Portanto, mesmo em Haskell, "paramétrico" e "irrestrito" não são realmente sinônimos - não me culpe, não é minha culpa!

No C ++, tanto a sobrecarga quanto as funções virtuais são polimorfismos ad-hoc. A definição de polimorfismo ad-hoc não se importa se a implementação é selecionada em tempo de execução ou tempo de compilação.

O C ++ fica muito próximo do polimorfismo paramétrico com modelos se todos os parâmetros do modelo tiverem tipo typename. Existem parâmetros de tipo e existe uma única implementação, independentemente de quais tipos são usados. No entanto, a regra "Falha na substituição não é um erro" significa que restrições implícitas surgem como resultado do uso de operações no modelo. Complicações adicionais incluem especialização de modelos para fornecer modelos alternativos - diferentes implementações (ad-hoc).

Portanto, de certa forma, o C ++ possui polimorfismo paramétrico, mas é implicitamente restrito e pode ser substituído por alternativas ad-hoc - ou seja, essa classificação realmente não funciona para o C ++.

Steve314
fonte
+1 Muitos pontos e idéias interessantes. Eu passei apenas algumas horas lendo sobre Haskell, então " aaqui está um tipo polimórfico irrestrito [...] para que não haja muito que possamos fazer com valores desse tipo". era interessante - em C ++ sans Concepts, você não está restrito a apenas tentar um conjunto específico de operações em um argumento de um tipo especificado como parâmetro de modelo ... bibliotecas como conceitos de otimização funcionam de outra maneira - garantindo que o tipo suporte operações você especifica, em vez de se proteger contra o uso acidental de operações adicionais.
Tony Delroy
@ Tony - Os conceitos são uma maneira de restringir explicitamente o polimorfismo dos modelos. Obviamente, as restrições implícitas não desaparecem por causa da compatibilidade, mas as restrições explícitas definitivamente melhorarão significativamente as coisas. Tenho certeza de que alguns planos anteriores de conceitos estavam um pouco relacionados às classes de tipo Haskell, embora eu não os tenha examinado tão profundamente e, quando olhei "superficialmente" pela última vez, não conhecia muito Haskell.
Steve314
"Obviamente, as restrições implícitas não desaparecem por causa da compatibilidade" - da memória, o C ++ 0x Concepts prometeu: - /) impedir "restrições implícitas" - você só podia usar o tipo da maneira prometida pelos Conceitos.
Tony Delroy
2

Quanto ao polimorfismo ad-hoc, significa sobrecarga de função ou sobrecarga do operador. Confira aqui:

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

Quanto ao polimorfismo paramétrico, as funções de gabarito também podem ser contadas porque não necessariamente levam parâmetros de tipos FIXED. Por exemplo, uma função pode classificar uma matriz de números inteiros e também pode classificar uma matriz de seqüências de caracteres, etc.

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

Eric Z
fonte
1
Infelizmente, embora correto, isso é enganador. As funções de modelo podem obter restrições implícitas por causa da regra SFINAE - o uso de uma operação dentro do modelo restringe implicitamente o polimorfismo - e a especialização de modelos pode fornecer modelos alternativos ad-hoc que substituem os modelos mais gerais. Portanto, um modelo (por padrão) fornece polimorfismo paramétrico irrestrito, mas não há aplicação disso - há pelo menos duas maneiras pelas quais ele pode se tornar restrito ou ad-hoc.
Steve314
De fato, seu exemplo - classificação - implica uma restrição. A classificação funciona apenas para os tipos pedidos (ou seja, forneça os <operadores e similares). Em Haskell, você expressar essa exigência explicitamente usando a classe Ord. O fato de você obter uma diferença <dependendo do tipo específico (conforme fornecido pela instância de Ord) seria considerado polimorfismo ad-hoc.
Steve314
2

Isso pode não ajudar em nada, mas fiz isso para apresentar meus amigos à programação, fornecendo funções definidas, como START, e ENDpara a função principal, para que não fosse muito assustador (eles usaram apenas o arquivo main.cpp ). Ele contém classes e estruturas polimórficas, modelos, vetores, matrizes, diretivas de pré-processador, amizade, operadores e ponteiros (todos os quais você provavelmente já deve saber antes de tentar o polimorfismo):

Nota: ainda não está concluído, mas você pode ter uma ideia

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;
            }
        }
    }
};
Joe
fonte
1

Aqui está um exemplo básico usando classes 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);
}
user2976089
fonte
0

Polimorfismo significa muitas formas, como tal, é usado para um operador agir de maneira diferente em diferentes instâncias. O polimorfismo é usado para implementar a herança. Por exemplo, definimos um fn draw () para um formato de classe, e o draw fn pode ser implementado para desenhar círculo, caixa, triângulo e outras formas. (que são objetos da forma da classe)

Jayraj Srikriti Naidu
fonte
-3

Se alguém disser corta para essas pessoas

The Surgeon
The Hair Stylist
The Actor

O que vai acontecer?

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.

Então, a representação acima mostra O que é polimorfismo (mesmo nome, comportamento diferente) em OOP.

Se você está indo para uma entrevista e o entrevistador pede que você conte / mostre um exemplo ao vivo para polimorfismo na mesma sala em que estamos, digamos:

Resposta - Porta / Janelas

Querendo saber como?

Através da porta / janela - uma pessoa pode entrar, o ar pode entrar, a luz pode entrar, a chuva pode entrar, etc.

Ou seja, uma forma de comportamento diferente (polimorfismo).

Para entender melhor e de uma maneira simples, usei o exemplo acima. Se você precisar de referência para o código, siga as respostas acima.

Sanchit
fonte
Como mencionei para melhor compreensão do polimorfismo em c ++, usei o exemplo acima. Isso pode ajudar um novato a realmente entender e relacionar qual é o significado ou o que está acontecendo por trás do código durante a execução na entrevista. Obrigado!
Sanchit
op perguntou "polimorfismo em c ++". sua resposta é muito abstrata.
StahlRat