Polimorfismo in C ++


129

PER QUANTO NE SO:

Il C ++ fornisce tre diversi tipi di polimorfismo.

  • Funzioni virtuali
  • Sovraccarico del nome della funzione
  • Sovraccarico dell'operatore

Oltre ai suddetti tre tipi di polimorfismo, esistono altri tipi di polimorfismo:

  • run-time
  • in fase di compilazione
  • polimorfismo ad hoc
  • polimorfismo parametrico

So che il polimorfismo di runtime può essere ottenuto con funzioni virtuali e il polimorfismo statico può essere ottenuto con funzioni template

Ma per gli altri due

  • polimorfismo ad hoc
  • polimorfismo parametrico che dice il sito web ,

polimorfismo ad hoc:

Se l'intervallo dei tipi effettivi che è possibile utilizzare è limitato e le combinazioni devono essere specificate individualmente prima dell'uso, questo si chiama polimorfismo ad hoc.

polimorfismo parametrico:

Se tutto il codice è scritto senza menzionare alcun tipo specifico e quindi può essere utilizzato in modo trasparente con un numero qualsiasi di nuovi tipi, si parla di polimorfismo parametrico.

Riesco a malapena a capirli :(

qualcuno può spiegarli entrambi, se possibile, con un esempio? Spero che le risposte a queste domande siano utili per molti nuovi passaporti delle loro università.


30
In realtà, il C ++ ha quattro tipi di polimorfismo: parametrico (generalità tramite modelli in C ++), inclusione (sottotipizzazione tramite metodi virtuali in C ++), sovraccarico e coercizione (conversioni implicite). Dal punto di vista concettuale, c'è poca distinzione tra sovraccarico della funzione e sovraccarico dell'operatore.
Fredoverflow

Quindi sembra che il sito web che ho citato sia fuorviante per molti ... ho ragione?
Vijay,

@zombie: quel sito web tocca molti buoni concetti, ma non è preciso e coerente nel suo uso della terminologia (ad esempio, una volta che inizia a parlare di polimorfismo di invio / runtime virtuale, fa molte affermazioni sul polimorfismo che sono sbagliate in generale ma vero per l'invio virtuale). Se hai già capito l'argomento, puoi relazionarti con ciò che viene detto e inserire mentalmente le avvertenze necessarie, ma è difficile arrivarci leggendo il sito ....
Tony Delroy,

Alcuni termini sono quasi sinonimi o più correlati ma più limitati rispetto ad altri termini. Ad esempio, nella mia esperienza, il termine "polimorfismo ad hoc" è usato principalmente in Haskell, tuttavia le "funzioni virtuali" sono strettamente correlate. La differenza minore è che "funzioni virtuali" è un termine orientato agli oggetti che si riferisce alle funzioni membro con "associazione tardiva". "Spedizione multipla" è anche una sorta di polimorfismo ad hoc. E come dice FredOverflow, sia il sovraccarico dell'operatore che quello della funzione sono sostanzialmente la stessa cosa.
Steve314,

Ho corretto la tua formattazione per te. Si prega di leggere la guida disponibile a destra del riquadro di modifica. Qualcuno con> 200 domande e> 3k dovrebbe conoscere queste cose di base. Inoltre, potresti voler acquistare una nuova tastiera. Il tasto shift di questo sembra fallire in modo intermittente. Oh, e: non esiste una "funzione template" in C ++. Vi sono, tuttavia, modelli di funzioni .
sbi,

Risposte:


219

Comprensione / requisiti per il polimorfismo

Comprendere il polimorfismo - come il termine è usato in Informatica - aiuta a partire da un semplice test e definizione di esso. Tener conto di:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Qui, f()è di eseguire alcune operazioni e viene dato valori xe ycome input.

Per esibire polimorfismo, f()deve essere in grado di operare con valori di almeno due tipi distinti (es. intE double), trovando ed eseguendo un codice distinto appropriato al tipo.


Meccanismi C ++ per polimorfismo

Polimorfismo esplicito specificato dal programmatore

Puoi scrivere in modo f()tale che possa operare su più tipi in uno dei seguenti modi:

  • pre-elaborazione:

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

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

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

    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

Altri meccanismi correlati

Il polimorfismo fornito dal compilatore per i tipi incorporati, le conversioni standard e il casting / coercizione sono discussi in seguito per completezza come:

  • sono comunque comunemente comprensibili intuitivamente (che garantisce una reazione " oh, quella "),
  • incidono sulla soglia nel richiedere e senza soluzione di continuità nell'uso i meccanismi di cui sopra e
  • la spiegazione è una seccante distrazione da concetti più importanti.

Terminologia

Ulteriore categorizzazione

Dati i meccanismi polimorfici sopra, possiamo classificarli in vari modi:

  • Quando viene selezionato il codice specifico del tipo polimorfico?

    • Tempo di esecuzione indica che il compilatore deve generare il codice per tutti i tipi che il programma potrebbe gestire durante l'esecuzione e, in fase di esecuzione, viene selezionato il codice corretto ( spedizione virtuale )
    • Il tempo di compilazione indica che durante la compilazione viene effettuata la scelta del codice specifico del tipo. Una conseguenza di ciò: diciamo che un programma chiamato solo fsopra con intargomenti - a seconda del meccanismo polimorfico usato e delle scelte incorporate per cui il compilatore potrebbe evitare di generare alcun codice f(double), o il codice generato potrebbe essere gettato via ad un certo punto della compilazione o del collegamento. ( tutti i meccanismi sopra tranne la spedizione virtuale )

  • Quali tipi sono supportati?

    • Significato ad-hoc che fornisci codice esplicito per supportare ogni tipo (es. Sovraccarico, specializzazione template); aggiungi esplicitamente il supporto "per questo" (secondo il significato ad hoc ), qualche altro "questo", e forse anche "quello" ;-).
    • Significato parametrico , puoi semplicemente provare a utilizzare la funzione per vari tipi di parametri senza fare specificamente nulla per abilitarne il supporto (ad es. Modelli, macro). Un oggetto con funzioni / operatori che si comportano come il modello / la macro si aspetta 1 è tutto ciò che il modello / la macro ha bisogno per fare il suo lavoro, con il tipo esatto irrilevante. I "concetti" introdotti da C ++ 20 esprimono e rafforzano tali aspettative - vedi la pagina cppreference qui .

      • Il polimorfismo parametrico fornisce la tipizzazione delle anatre - un concetto attribuito a James Whitcomb Riley che apparentemente ha detto "Quando vedo un uccello che cammina come un'anatra e nuota come un'anatra e cigola come un'anatra, lo chiamo un'anatra". .

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
    • Il polimorfismo del sottotipo (aka inclusione) consente di lavorare su nuovi tipi senza aggiornare l'algoritmo / la funzione, ma devono essere derivati ​​dalla stessa classe di base (invio virtuale)

1 - I modelli sono estremamente flessibili. SFINAE (vedi anche std::enable_if) consente effettivamente diverse serie di aspettative per il polimorfismo parametrico. Ad esempio, potresti codificare che quando il tipo di dati che stai elaborando ha un .size()membro, utilizzerai una funzione, altrimenti un'altra funzione che non è necessaria .size()(ma presumibilmente soffre in qualche modo, ad esempio utilizzando il più lento strlen()o non stampare come utile un messaggio nel registro). È inoltre possibile specificare comportamenti ad hoc quando il modello viene istanziato con parametri specifici, lasciando alcuni parametri parametrici ( specializzazione parziale del modello ) o meno ( specializzazione completa ).

"Polimorfica"

Alf Steinbach commenta che nello standard C ++ il polimorfismo si riferisce solo al polimorfismo di runtime usando l'invio virtuale. Comp. Generale Sci. il significato è più inclusivo, secondo il glossario del creatore C ++ Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):

polimorfismo: fornire un'unica interfaccia a entità di diverso tipo. Le funzioni virtuali forniscono polimorfismo dinamico (runtime) attraverso un'interfaccia fornita da una classe base. Le funzioni e i template sovraccaricati forniscono polimorfismo statico (tempo di compilazione). TC ++ PL 12.2.6, 13.6.1, D&E 2.9.

Questa risposta - come la domanda - mette in relazione le funzionalità C ++ con il Comp. Sci. terminologia.

Discussione

Con lo standard C ++ si usa una definizione più ristretta di "polimorfismo" rispetto al Comp. Sci. comunità, per garantire la comprensione reciproca per il tuo pubblico considera ...

  • usando una terminologia non ambigua ("possiamo rendere questo codice riutilizzabile per altri tipi?" o "possiamo usare il dispacciamento virtuale?" piuttosto che "possiamo rendere questo codice polimorfico?"), e / o
  • definire chiaramente la tua terminologia.

Tuttavia, ciò che è cruciale per essere un grande programmatore C ++ è capire cosa sta realmente facendo il polimorfismo per te ...

    che consente di scrivere una volta il codice "algoritmico" e quindi applicarlo a molti tipi di dati

... e quindi sii molto consapevole di come i diversi meccanismi polimorfici soddisfano i tuoi bisogni reali.

Polimorfismi run-time:

  • input elaborato con metodi di fabbrica e sputato come una raccolta di oggetti eterogenea gestita tramite Base*s,
  • implementazione scelta in fase di esecuzione in base a file di configurazione, opzioni della riga di comando, impostazioni dell'interfaccia utente ecc.,
  • l'implementazione variava in fase di esecuzione, ad esempio per un modello di macchina a stati.

Quando non esiste un chiaro driver per il polimorfismo di runtime, le opzioni di compilazione sono spesso preferibili. Tener conto di:

  • l'aspetto di compilazione-quello che viene chiamato delle classi basate su modelli è preferibile alle interfacce fat che falliscono in fase di esecuzione
  • SFINAE
  • CRTP
  • ottimizzazioni (molte tra cui inline ed eliminazione di dead code, srotolamento di loop, array basati su stack statici vs heap)
  • __FILE__, __LINE__concatenazione letterale di stringhe e altre capacità uniche di macro (che rimangono malvagie ;-))
  • modelli e macro testano l'utilizzo semantico supportato, ma non limitano artificialmente il modo in cui viene fornito quel supporto (come tende la spedizione virtuale richiedendo esattamente le sostituzioni della funzione membro corrispondente)

Altri meccanismi a supporto del polimorfismo

Come promesso, per completezza vengono trattati diversi argomenti periferici:

  • sovraccarichi forniti dal compilatore
  • conversioni
  • calchi / coercizione

Questa risposta si conclude con una discussione sul modo in cui quanto sopra si combina per potenziare e semplificare il codice polimorfico - in particolare il polimorfismo parametrico (modelli e macro).

Meccanismi per la mappatura su operazioni specifiche del tipo

> Sovraccarichi impliciti forniti dal compilatore

Concettualmente, il compilatore sovraccarica molti operatori per i tipi predefiniti. Non è concettualmente diverso dal sovraccarico specificato dall'utente, ma è elencato in quanto facilmente trascurabile. Ad esempio, è possibile aggiungere a ints e doubles utilizzando la stessa notazione x += 2e il compilatore produce:

  • istruzioni specifiche per CPU
  • un risultato dello stesso tipo.

Il sovraccarico si estende quindi perfettamente ai tipi definiti dall'utente:

std::string x;
int y = 0;

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

I sovraccarichi forniti dal compilatore per i tipi di base sono comuni nei linguaggi informatici di alto livello (3GL +) e la discussione esplicita del polimorfismo implica generalmente qualcosa di più. (2GLs - linguaggi di assemblaggio - spesso richiedono al programmatore di usare esplicitamente mnemonici diversi per tipi diversi.)

> Conversioni standard

La quarta sezione dello standard C ++ descrive le conversioni standard.

Il primo punto riassume bene (da una vecchia bozza - si spera ancora sostanzialmente corretto):

-1- Le conversioni standard sono conversioni implicite definite per i tipi predefiniti. La clausola conv enumera la serie completa di tali conversioni. Una sequenza di conversione standard è una sequenza di conversioni standard nel seguente ordine:

  • Zero o una conversione dal seguente set: conversione da lvalue a rvalue, conversione da array a puntatore e conversione da funzione a puntatore.

  • Zero o una conversione dal seguente set: promozioni integrali, promozione in virgola mobile, conversioni integrali, conversioni in virgola mobile, conversioni in virgola mobile integrale, conversioni da puntatore, conversioni da puntatore a membro e conversioni booleane.

  • Zero o una conversione di qualificazione.

[Nota: una sequenza di conversione standard può essere vuota, ovvero non può consistere in conversioni. ] Se necessario, una sequenza di conversione standard verrà applicata a un'espressione per convertirla in un tipo di destinazione richiesto.

Queste conversioni consentono codice come:

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

a(3.14);
a(42);

Applicazione del test precedente:

Per essere polimorfico, [ a()] deve essere in grado di operare con valori di almeno due tipi distinti (ad es. intE double), trovando ed eseguendo il codice di tipo appropriato .

a()si esegue il codice specifico per doubleed è quindi non polimorfica.

Ma, in seconda convocazione per a()il compilatore sa per generare il codice di tipo appropriato per un "punto di promozione flottante" (Standard § 4) per convertire 42a 42.0. Quel codice extra è nella funzione chiamante . Discuteremo il significato di questo nella conclusione.

> Coercizione, cast, costruttori impliciti

Questi meccanismi consentono alle classi definite dall'utente di specificare comportamenti affini alle conversioni standard dei tipi predefiniti. Diamo un'occhiata:

int a, b;

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

Qui, l'oggetto std::cinviene valutato in un contesto booleano, con l'aiuto di un operatore di conversione. Questo può essere concettualmente raggruppato con "promozioni integrali" e altri dalle conversioni standard nell'argomento sopra.

I costruttori impliciti fanno effettivamente la stessa cosa, ma sono controllati dal tipo cast-to:

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

Implicazioni di sovraccarichi forniti dal compilatore, conversioni e coercizione

Tener conto di:

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

Se vogliamo che l'importo xvenga trattato come un numero reale durante la divisione (ovvero sia 6,5 ​​anziché arrotondato per difetto a 6), dobbiamo solo cambiare typedef double Amount.

È bello, ma non sarebbe stato troppo lavoro per rendere esplicitamente il codice "tipo corretto":

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

Ma considera che possiamo trasformare la prima versione in un template:

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

È a causa di quelle piccole "funzionalità di praticità" che può essere facilmente istanziato per o into doublee come previsto. Senza queste funzionalità, avremmo bisogno di cast espliciti, tratti di tipo e / o classi di policy, un pasticcio verboso e soggetto a errori come:

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

Pertanto, sovraccarico dell'operatore fornito dal compilatore per tipi incorporati, conversioni standard, costruttori di casting / coercizione / impliciti - tutti contribuiscono in modo sottile al polimorfismo. Dalla definizione nella parte superiore di questa risposta, affrontano "la ricerca e l'esecuzione del codice appropriato per tipo" mappando:

  • "lontano" dai tipi di parametri

    • dai numerosi tipi di dati handle di codice algoritmico polimorfico

    • al codice scritto per una (potenzialmente minore) numero (i medesimi o altri) tipi.

  • "a" tipi parametrici da valori di tipo costante

Essi non stabiliscono contesti polimorfici da soli, ma aiutano empower / code semplificare all'interno di tali contesti.

Potresti sentirti tradito ... non sembra molto. Il significato è che in contesti polimorfici parametrici (cioè all'interno di modelli o macro), stiamo cercando di supportare una gamma arbitrariamente ampia di tipi ma spesso vogliamo esprimere operazioni su di essi in termini di altre funzioni, valori letterali e operazioni progettati per un piccolo set di tipi. Riduce la necessità di creare funzioni o dati quasi identici per tipo, quando l'operazione / valore è logicamente lo stesso. Queste caratteristiche cooperano per aggiungere un atteggiamento di "miglior sforzo", facendo ciò che è intuitivamente previsto utilizzando le funzioni e i dati disponibili limitati e fermandosi con un errore solo quando c'è vera ambiguità.

Questo aiuta a limitare la necessità di un codice polimorfico che supporti il ​​codice polimorfico, disegnando una rete più stretta attorno all'uso del polimorfismo in modo che l'uso localizzato non imponga un uso diffuso e rende disponibili i benefici del polimorfismo secondo necessità senza imporre i costi di dover esporre l'implementazione a tempo di compilazione, disporre di più copie della stessa funzione logica nel codice oggetto per supportare i tipi utilizzati e nel fare invio virtuale invece di chiamate inline o almeno in fase di compilazione risolte. Come è tipico in C ++, al programmatore viene data molta libertà di controllare i confini entro i quali viene utilizzato il polimorfismo.


1
-1 Ottima risposta tranne per la discussione terminologica. Lo standard C ++ definisce il termine "polimorfico" in §1.8 / 1, facendo riferimento alla sezione 10.3 relativa alle funzioni virtuali. Quindi non c'è spazio per le oscillazioni, non c'è spazio per la discussione, non c'è spazio per l'opinione personale: nel contesto del C ++ standard quel termine è definito una volta per tutte. E gioca un ruolo pratico. Ad esempio, §5.2.7 / 6 about dynamic_castrichiede un "puntatore o un valore di un tipo polimorfico". Saluti e hth.,
Saluti e hth. - Alf

@Alf: ottimo riferimento - anche se penso che la tua prospettiva sia troppo stretta. È chiaro dalla domanda che elenca il sovraccarico, il polimorfismo ad hoc e parametrico ecc. Che una risposta dovrebbe mettere in relazione le capacità del C ++ con il Comp generale. Sci. significato dei termini. Infatti, il glossario di Stroustrup dice "polimorfismo: fornire una singola interfaccia a entità di diverso tipo. Le funzioni virtuali forniscono polimorfismo dinamico (runtime) attraverso un'interfaccia fornita da una classe di base. Funzioni e template sovraccarichi forniscono polimorfismo statico (tempo di compilazione). TC ++ PL 12.2.6, 13.6.1, D&E 2.9. "
Tony Delroy,

@Tony: non è la spinta principale della tua risposta è sbagliata. va bene, va benissimo. è solo quel wrt. la terminologia è capovolta: la terminologia accademica formale è quella ristretta definita dallo Holy International Standard e la terminologia informale approssimativa in cui le persone possono significare cose leggermente diverse, è quella usata principalmente in questa domanda e risposta. Saluti e hth.,
Saluti e hth. - Alf

@Alf: Vorrei che la risposta fosse ottima - "Altri meccanismi" devono essere riscritti in un quinto dei versi e sto contemplando / elaborando caratteristiche e implicazioni più concrete che contrastano i meccanismi polimorfici. Comunque, la mia comprensione è quella accademica formale esclusivamente incentrato sul C ++ può essere limitato, ma il Comp accademico formale generale Comp. Sci. il significato non è, come evidenziato dal glossario di Stroustrup. Abbiamo bisogno di qualcosa di definitivo, ad esempio la definizione di Knuth, senza fortuna che cerca su Google. Apprezzo che tu sia un guru del C ++, ma puoi indicare prove pertinenti su questo in particolare?
Tony Delroy,

1
@Alf: in secondo luogo, sono fiducioso che il polimorfismo sia formalmente definito in qualsiasi Comp generale decente. Sci. libro in un modo (senza tempo, stabile) compatibile con il mio utilizzo (e quello di Stroustrup). L'articolo di Wikipedia collega alcune pubblicazioni accademiche che lo definiscono in questo modo: "Le funzioni polimorfiche sono funzioni i cui operandi (parametri effettivi) possono avere più di un tipo. I tipi polimorfici sono tipi le cui operazioni sono applicabili a valori di più di un tipo". (da lucacardelli.name/Papers/OnUnderstanding.A4.pdf ). Quindi, la domanda è "chi parla per Comp. Sci" ...?
Tony Delroy,

15

In C ++, la distinzione importante è l'associazione run-time vs. compilazione-tempo. Ad-hoc vs. parametric non aiuta davvero, come spiegherò più avanti.

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

Nota: il polimorfismo di runtime può ancora essere risolto in fase di compilazione, ma questa è solo un'ottimizzazione. La necessità di supportare la risoluzione di runtime in modo efficiente e il trading contro altri problemi, fa parte di ciò che ha portato alle funzioni virtuali di essere ciò che sono. E questo è davvero fondamentale per tutte le forme di polimorfismo in C ++ - ognuna nasce da diversi set di compromessi realizzati in un contesto diverso.

Il sovraccarico delle funzioni e il sovraccarico dell'operatore sono la stessa cosa in tutti i modi che contano. I nomi e la sintassi per usarli non influiscono sul polimorfismo.

I modelli consentono di specificare molti sovraccarichi di funzioni contemporaneamente.

C'è un altro set di nomi per la stessa idea del tempo di risoluzione ...

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

Questi nomi sono più associati a OOP, quindi è un po 'strano dire che un modello o un'altra funzione non membro utilizza l'associazione anticipata.

Per comprendere meglio la relazione tra funzioni virtuali e sovraccarico di funzioni, è anche utile comprendere la differenza tra "invio singolo" e "invio multiplo". L'idea può essere intesa come una progressione ...

  • Innanzitutto, ci sono funzioni monomorfe. L'implementazione della funzione è identificata in modo univoco dal nome della funzione. Nessuno dei parametri è speciale.
  • Quindi, c'è un singolo invio. Uno dei parametri è considerato speciale e utilizzato (insieme al nome) per identificare quale implementazione utilizzare. In OOP, tendiamo a pensare a questo parametro come "l'oggetto", elencarlo prima del nome della funzione ecc.
  • Quindi, ci sono più spedizioni. Qualsiasi / tutti i parametri contribuiscono a identificare quale implementazione utilizzare. Pertanto, ancora una volta, nessuno dei parametri deve essere speciale.

Ovviamente c'è di più in OOP di una scusa per nominare un parametro come speciale, ma questa è una parte di esso. E in relazione a ciò che ho detto sui compromessi: la singola spedizione è abbastanza facile da eseguire in modo efficiente (la solita implementazione si chiama "tabelle virtuali"). L'invio multiplo è più imbarazzante, non solo in termini di efficienza, ma anche per una compilazione separata. Se sei curioso, potresti cercare "il problema dell'espressione".

Così come è un po 'strano usare il termine "associazione anticipata" per le funzioni non membro, è un po' strano usare i termini "invio singolo" e "invio multiplo" in cui il polimorfismo viene risolto al momento della compilazione. In genere, si ritiene che C ++ non disponga di spedizioni multiple, che è considerato un tipo particolare di risoluzione di runtime. Tuttavia, il sovraccarico delle funzioni può essere visto come invio multiplo eseguito in fase di compilazione.

Tornando al polimorfismo parametrico ad hoc, questi termini sono più popolari nella programmazione funzionale e non funzionano del tutto in C ++. Comunque...

Polimorfismo parametrico significa che hai tipi come parametri e lo stesso codice esatto viene utilizzato indipendentemente dal tipo che usi per quei parametri.

Il polimorfismo ad hoc è ad hoc, nel senso che si fornisce un codice diverso a seconda dei tipi particolari.

Il sovraccarico e le funzioni virtuali sono entrambi esempi di polimorfismo ad hoc.

Ancora una volta, ci sono alcuni sinonimi ...

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

Tranne il fatto che questi non sono abbastanza sinonimi, sebbene siano comunemente trattati come se lo fossero, ed è qui che è probabile che sorga confusione in C ++.

Il ragionamento alla base del trattamento di questi come sinonimi è che vincolando il polimorfismo a particolari classi di tipi, diventa possibile usare operazioni specifiche per quelle classi di tipi. La parola "classi" qui può essere interpretata in senso OOP, ma in realtà si riferisce solo a insiemi (di solito nominati) di tipi che condividono determinate operazioni.

Quindi il polimorfismo parametrico viene solitamente assunto (almeno di default) per implicare un polimorfismo non vincolato. Poiché lo stesso codice viene utilizzato indipendentemente dai parametri del tipo, le uniche operazioni supportabili sono quelle che funzionano per tutti i tipi. Lasciando la serie di tipi non vincolata, si limita fortemente la serie di operazioni che è possibile applicare a tali tipi.

Ad esempio Haskell, puoi avere ...

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

Il aqui è un tipo polimorfico senza vincoli. Potrebbe essere qualsiasi cosa, quindi non c'è molto che possiamo fare con valori di quel tipo.

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

Qui, aè costretto a essere un membro della Numclasse - tipi che agiscono come numeri. Questo vincolo ti consente di fare cose numeriche con quei valori, come aggiungerli. Anche l' 3inferenza di tipo polimorfico capisce che si intende il 3tipo a.

Penso a questo come un polimorfismo parametrico vincolato. C'è solo una implementazione, ma può essere applicata solo in casi vincolati. L'aspetto ad hoc è la scelta di quale +e 3da utilizzare. Ogni "istanza" di Numha una propria implementazione distinta di questi. Quindi anche in Haskell "parametrico" e "non vincolato" non sono in realtà sinonimi - non incolparmi, non è colpa mia!

In C ++, entrambe le funzioni di sovraccarico e virtuali sono polimorfismi ad hoc. La definizione di polimorfismo ad hoc non importa se l'implementazione è selezionata in fase di esecuzione o in fase di compilazione.

Il C ++ si avvicina molto al polimorfismo parametrico con i template se ogni parametro template ha tipo typename . Esistono parametri di tipo e un'unica implementazione, indipendentemente dai tipi utilizzati. Tuttavia, la regola "Errore di sostituzione non è un errore" indica che sorgono vincoli impliciti a seguito dell'utilizzo di operazioni all'interno del modello. Ulteriori complicazioni includono la specializzazione dei modelli per la fornitura di modelli alternativi - diverse implementazioni (ad-hoc).

Quindi, in un certo senso, il C ++ ha un polimorfismo parametrico, ma è implicitamente vincolato e potrebbe essere ignorato da alternative ad hoc - cioè questa classificazione non funziona davvero per C ++.


+1 Un sacco di punti interessanti e approfondimenti. Ho trascorso solo poche ore a leggere su Haskell, quindi " aecco un tipo polimorfico senza vincoli [...] quindi non c'è molto che possiamo fare con valori di quel tipo". era interessante - in C ++ sans Concetti non sei limitato a tentare solo un insieme specifico di operazioni su un argomento di un tipo specificato come parametro modello ... librerie come i concetti di boost funzionano nell'altro modo - assicurandoti che il tipo supporti le operazioni si specifica, anziché proteggersi dall'uso accidentale di operazioni aggiuntive.
Tony Delroy,

@Tony - I concetti sono un modo per limitare esplicitamente il polimorfismo dei modelli. I vincoli impliciti ovviamente non scompariranno a causa della compatibilità, ma i vincoli espliciti miglioreranno sicuramente le cose in modo significativo. Sono abbastanza sicuro che alcuni piani passati per concetti fossero in qualche modo correlati alle macchine da scrivere di Haskell, anche se non le ho esaminate così profondamente e l'ultima volta che ho guardato "superficialmente" non conoscevo molto Haskell.
Steve314,

"I vincoli impliciti ovviamente non scompariranno a causa della compatibilità" - dalla memoria, C ++ 0x Concepts ha (promesso di: - /) prevenire "vincoli impliciti" - si potrebbe usare il tipo solo nei modi promessi dai Concetti.
Tony Delroy,

2

Per quanto riguarda il polimorfismo ad hoc, significa sovraccarico della funzione o sovraccarico dell'operatore. Dai un'occhiata qui:

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

Per quanto riguarda il polimorfismo parametrico, è possibile contare anche le funzioni del modello perché non necessariamente comprendono parametri di tipo FISSO. Ad esempio, una funzione può ordinare array di numeri interi e può anche ordinare array di stringhe, ecc.

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


1
Sfortunatamente, sebbene corretto, questo è fuorviante. Le funzioni dei modelli possono ottenere vincoli impliciti a causa della regola SFINAE - l'utilizzo di un'operazione all'interno del modello vincola implicitamente il polimorfismo - e la specializzazione dei modelli può fornire modelli alternativi ad hoc che prevalgono sui modelli più generali. Quindi un modello (per impostazione predefinita) fornisce un polimorfismo parametrico senza vincoli, ma non è possibile applicarlo - ci sono almeno due modi in cui può diventare vincolato o ad hoc.
Steve314,

In effetti il ​​tuo esempio - l'ordinamento - implica un vincolo. L'ordinamento funziona solo per i tipi ordinati (ad esempio fornire <operatori simili e simili). In Haskell, esprimevi quel requisito esplicitamente usando la classe Ord. Il fatto che tu ne ottenga una diversa a <seconda del tipo particolare (come fornito dall'istanza di Ord) sarebbe considerato polimorfismo ad hoc.
Steve314,

2

Questo potrebbe non essere di alcun aiuto, ma l'ho fatto per presentare i miei amici alla programmazione dando funzioni definite, come START, e ENDper la funzione principale, quindi non era troppo scoraggiante (hanno usato solo il file main.cpp ). Contiene classi e strutture polimorfiche, modelli, vettori, matrici, direttive preproccessori, amicizia, operatori e puntatori (che probabilmente dovresti sapere prima di tentare il polimorfismo):

Nota: non è finito, ma è possibile ottenere l'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

Ecco un esempio di base usando le classi polimorfiche

#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 che molte forme in quanto tali vengono utilizzate da un operatore per agire diversamente in casi diversi. Il polimorfismo viene utilizzato per implementare l'ereditarietà. Ad esempio, abbiamo definito un disegno fn () per una forma di classe, quindi il disegno fn può essere implementato per disegnare cerchio, riquadro, triangolo e altre forme. (che sono oggetti della forma della classe)


-3

Se qualcuno dice TAGLIO a queste persone

The Surgeon
The Hair Stylist
The Actor

Cosa accadrà?

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.

Quindi la rappresentazione sopra mostra cos'è il polimorfismo (stesso nome, comportamento diverso) in OOP.

Se stai per un'intervista e un intervistatore ti chiede di dire / mostrare un esempio dal vivo per il polimorfismo nella stessa stanza in cui siamo seduti, diciamo-

Risposta - Porta / Windows

Ti chiedi come?

Attraverso la porta / finestra: una persona può venire, l'aria può venire, la luce può venire, la pioggia può venire, ecc.

cioè Una forma di comportamento diverso (polimorfismo).

Per capirlo meglio e in modo semplice ho usato l'esempio sopra. Se hai bisogno di riferimento per il codice segui le risposte sopra.


Come ho detto per una migliore comprensione del polimorfismo in c ++, ho usato l'esempio sopra. Questo potrebbe aiutare un più fresco a capire e mettere in relazione ciò che è il significato o ciò che sta accadendo dietro il codice durante l'intervista. Grazie!
Sanchit,

op ha chiesto "polimorfismo in c ++". la tua risposta è troppo astratta.
StahlRat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.