C ++ 'da polimorfizm


129

BİLDİĞİM KADARIYLA:

C ++, üç farklı polimorfizm türü sağlar.

  • Sanal işlevler
  • Fonksiyon adı aşırı yükleme
  • Operatör aşırı yükleme

Yukarıdaki üç polimorfizm türüne ek olarak, başka tür polimorfizm de vardır:

  • Çalışma süresi
  • Derleme zamanı
  • geçici polimorfizm
  • parametrik çok biçimlilik

Bunu biliyorum zamanı polimorfizmi elde edilebilir sanal fonksiyonlar ve statik polimorfizm elde edilebilir şablon fonksiyonları

Ama diğer ikisi için

geçici polimorfizm:

Kullanılabilecek gerçek türlerin aralığı sonluysa ve kombinasyonların kullanımdan önce ayrı ayrı belirtilmesi gerekiyorsa, buna geçici polimorfizm denir.

parametrik polimorfizm:

Tüm kod herhangi bir özel tipten bahsedilmeden yazılırsa ve bu nedenle herhangi bir sayıda yeni türle şeffaf olarak kullanılabilirse, buna parametrik polimorfizm denir.

Onları pek anlayamıyorum :(

biri mümkünse ikisini de bir örnekle açıklayabilir mi? Umarım bu soruların cevapları, kolejlerinden birçok yeni geçiş için yardımcı olur.


30
Aslında, C ++ 'nın dört tür polimorfizmi vardır: parametrik (C ++' daki şablonlar aracılığıyla genellik), dahil etme (C ++ 'da sanal yöntemler aracılığıyla alt tipleme), aşırı yükleme ve zorlama (örtük dönüştürmeler). Kavramsal olarak, fonksiyon aşırı yükleme ve operatör aşırı yükleme arasında çok az fark vardır.
fredoverflow

Görünüşe göre bahsettiğim web sitesi pek çok kişiyi yanıltıyor .. Doğru mu?
Vijay

@zombie: bu web sitesi birçok iyi kavrama değiniyor, ancak terminoloji kullanımında kesin ve tutarlı değil (örneğin, sanal gönderme / çalışma zamanı polimorfizminden bahsetmeye başladığında, çok biçimlilik hakkında yanlış birçok açıklama yapıyor genel olarak ancak sanal gönderim için doğrudur). Konuyu zaten anladıysanız, söylenenlerle bağlantı kurabilir ve gerekli uyarıları zihinsel olarak ekleyebilirsiniz, ancak siteyi okuyarak oraya ulaşmak zor ....
Tony Delroy

Bazı terimler neredeyse eşanlamlıdır veya diğer terimlerle daha fazla ilgilidir ancak diğer terimlerden daha kısıtlıdır. Örneğin, "ad-hoc polimorfizm" terimi, benim deneyimime göre Haskell'de çoğunlukla kullanılmaktadır, ancak "sanal işlevler" çok yakından ilişkilidir. Küçük fark, "sanal işlevlerin", "geç bağlanma" ile üye işlevlere atıfta bulunan nesne yönelimli bir terim olmasıdır. "Çoklu gönderim" de bir tür geçici polimorfizmdir. Ve FredOverflow'un dediği gibi, hem operatör hem de işlev aşırı yüklemesi temelde aynı şeydir.
Steve314

Biçimlendirmenizi sizin için düzelttim. Lütfen düzenleme bölmesinin sağında bulunan yardımı okuyun. > 200 sorusu ve> 3k sorusu olan biri bu temel bilgileri bilmelidir. Ayrıca yeni bir klavye satın almak isteyebilirsiniz. Bu birinin shift tuşu ara sıra başarısız oluyor gibi görünüyor. Oh, ve: C ++ ' da "şablon işlevi" diye bir şey yoktur . Bununla birlikte, işlev şablonları vardır .
sbi

Yanıtlar:


219

Polimorfizmin anlaşılması / gereksinimleri

Polimorfizmi anlamak - terim Hesaplama Biliminde kullanıldığı gibi - onun için basit bir testten ve tanımından başlamak yardımcı olur. Düşünmek:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

Burada bir f()işlem yapmaktır ve değerler xve ygirdi olarak verilmektedir .

Polimorfizm sergilemek f()için, en az iki farklı türdeki değerlerle (örneğin intve double) çalışabilmeli, ayrı tipe uygun kod bulmalı ve çalıştırabilmelidir.


Polimorfizm için C ++ mekanizmaları

Açık programcı tarafından belirtilen çok biçimlilik

f()Aşağıdaki yollardan herhangi biriyle birden çok türde çalışabilecek şekilde yazabilirsiniz :

  • Ön işleme:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
  • Aşırı yükleme:

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

    template <typename T>
    void f(T& x) { x += 2; }
  • Sanal gönderim:

    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

Diğer ilgili mekanizmalar

Yerleşik türler için derleyici tarafından sağlanan polimorfizm, Standart dönüştürmeler ve döküm / zorlama daha sonra tamlık için şu şekilde tartışılacaktır:

  • genellikle sezgisel olarak anlaşılırlar (bir " ah, bu " tepkiyi garanti eder )
  • Yukarıdaki mekanizmaları zorunlu kılma ve kullanımdaki kusursuzluğu etkilerler ve
  • açıklama, daha önemli kavramlardan ciddi bir dikkat dağıtmadır.

terminoloji

Daha fazla sınıflandırma

Yukarıdaki polimorfik mekanizmalar göz önüne alındığında, bunları çeşitli şekillerde sınıflandırabiliriz:

  • Polimorfik türe özgü kod ne zaman seçilir?

    • Çalışma süresi , derleyicinin programın çalışırken işleyebileceği tüm türler için kod üretmesi gerektiği anlamına gelir ve çalışma zamanında doğru kod seçilir ( sanal dağıtım )
    • Derleme süresi , türe özgü kod seçiminin derleme sırasında yapıldığı anlamına gelir. Bunun bir sonucu: Diyelim ki fyukarıda sadece intargümanlarla çağrılan bir program - kullanılan polimorfik mekanizmaya ve satır içi seçeneklere bağlı olarak, derleyicinin herhangi bir kod üretmekten kaçınabileceği f(double)veya oluşturulan kod, derleme veya bağlantı sırasında bir noktada atılabilir. ( sanal gönderim hariç yukarıdaki tüm mekanizmalar )

  • Hangi türler destekleniyor?

    • Ad-hoc, her türü desteklemek için açık kod sağladığınız anlamına gelir (örneğin, aşırı yükleme, şablon uzmanlığı); açıkça "bunun için" ( ad hoc'un anlamına göre) türü, başka bir "bu" ve belki "o" da ;-) ekliyorsunuz.
    • Parametrik anlamı, işlevi çeşitli parametre türleri için, özellikle onları desteklemek için herhangi bir şey yapmadan kullanmaya çalışabilirsiniz (örn. Şablonlar, makrolar). Şablon / makro beklentiden gibi hareket olduğunu işlevleri / operatörleri ile bir nesne 1 olan şablon / makro ihtiyaçlar kesin türü ilgisiz olmak üzere işini yapması, hepsi bu. C ++ 20 tarafından sunulan "kavramlar" bu tür beklentileri ifade eder ve uygular - buradaki cppreference sayfasına bakın .

      • Parametrik polimorfizm ördek tiplemesini sağlar - James Whitcomb Riley'ye atfedilen ve görünüşe göre "Ördek gibi yürüyen, ördek gibi yüzen ve ördek gibi şarlatan bir kuş gördüğümde, bu kuşa ördek derim." .

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
    • Alt tür (diğer adıyla dahil etme) polimorfizmi , algoritmayı / işlevi güncellemeden yeni türler üzerinde çalışmanıza izin verir, ancak bunlar aynı temel sınıftan (sanal gönderim) türetilmelidir.

1 - Şablonlar son derece esnektir. SFINAE (ayrıca bakınız std::enable_if) parametrik polimorfizm için çeşitli beklentilere etkin bir şekilde izin verir. Örneğin, .size()işlediğiniz veri türünün bir üyesi olduğunda, bir işlevi kullanacağınızı, aksi takdirde ihtiyaç duymayan .size()(ancak muhtemelen bir şekilde zarar gören başka bir işlevi) kodlayabilirsiniz - örneğin daha yavaş kullanmak strlen()veya yazdırmamak günlükte yararlı bir mesaj). Ayrıca, şablon belirli parametrelerle örneklendiğinde, bazı parametreleri parametrik bırakarak ( kısmi şablon özelleştirme ) veya bırakmayarak ( tam özelleştirme ) geçici davranışları da belirtebilirsiniz .

"Polimorfik"

Alf Steinbach, C ++ Standardı polimorfikte yalnızca sanal gönderim kullanan çalışma zamanı polimorfizmine atıfta bulunduğunu belirtmektedir. Genel Comp. Sci. C ++ yaratıcısı Bjarne Stroustrup'un sözlüğüne ( http://www.stroustrup.com/glossary.html ) göre anlamı daha kapsayıcıdır :

polimorfizm - farklı türlerdeki varlıklara tek bir arayüz sağlar. Sanal işlevler, bir temel sınıf tarafından sağlanan bir arabirim aracılığıyla dinamik (çalışma zamanı) çok biçimlilik sağlar. Aşırı yüklenmiş işlevler ve şablonlar, statik (derleme zamanı) çok biçimlilik sağlar. TC ++ PL 12.2.6, 13.6.1, D&E 2.9.

Bu cevap - soru gibi - C ++ özelliklerini Comp ile ilişkilendirir. Sci. terminoloji.

Tartışma

C ++ Standardı ile Comp'dan daha dar bir "polimorfizm" tanımı kullanılır. Sci. için topluluk, karşılıklı anlayış sağlamak için sizin seyirci düşünün ...

  • belirsiz olmayan terminoloji kullanarak ("bu kodu diğer türler için yeniden kullanılabilir hale getirebilir miyiz?" veya "bu kodu polimorfik yapabilir miyiz?" yerine "sanal gönderimi kullanabilir miyiz?") ve / veya
  • terminolojinizi açıkça tanımlayan.

Yine de, harika bir C ++ programcısı olmanın en önemli yanı, polimorfizmin sizin için gerçekten ne yaptığını anlamaktır ...

    "algoritmik" kodu bir kez yazmanıza ve ardından bunu birçok veri türüne uygulamanıza olanak tanır

... ve sonra farklı polimorfik mekanizmaların gerçek ihtiyaçlarınızla nasıl eşleştiğinin farkında olun.

Çalışma zamanı polimorfizm takımları:

  • fabrika yöntemleriyle işlenen ve Base*s aracılığıyla işlenen heterojen bir nesne koleksiyonu olarak tükenen girdi ,
  • çalışma zamanında yapılandırma dosyalarına, komut satırı anahtarlarına, UI ayarlarına vb. göre seçilen uygulama,
  • uygulama, bir durum makinesi modeli gibi çalışma zamanında değişiklik gösterdi.

Çalışma zamanı polimorfizmi için net bir sürücü olmadığında, derleme zamanı seçenekleri genellikle tercih edilir. Düşünmek:

  • templated sınıfların derleme özelliği, çalışma zamanında başarısız olan yağ arayüzlerine tercih edilir
  • SFINAE
  • CRTP
  • optimizasyonlar (çoğu satır içi ve ölü kod ortadan kaldırma, döngü açma, statik yığın tabanlı diziler ve yığın gibi)
  • __FILE__,, __LINE__dize değişmez birleştirme ve makroların diğer benzersiz yetenekleri (kötü kalır ;-))
  • şablonlar ve makrolar test anlamsal kullanımı desteklenir, ancak bu desteğin nasıl sağlandığını yapay olarak kısıtlamayın (sanal gönderim, tam olarak eşleşen üye işlev geçersiz kılmaları gerektirerek eğilimindedir)

Polimorfizmi destekleyen diğer mekanizmalar

Söz verildiği gibi, eksiksizlik için birkaç çevresel konu ele alınmıştır:

  • derleyici tarafından sağlanan aşırı yüklemeler
  • dönüşümler
  • silendirler / zorlama

Bu cevap, polimorfik kodu - özellikle parametrik polimorfizmi (şablonlar ve makrolar) güçlendirmek ve basitleştirmek için yukarıdakilerin nasıl birleştiğine dair bir tartışma ile son bulur.

Türe özgü işlemlerle eşleştirme mekanizmaları

> Derleyici tarafından sağlanan örtük aşırı yüklemeler

Kavramsal olarak, derleyici yerleşik türler için birçok operatörü aşırı yükler . Kullanıcı tarafından belirlenen aşırı yüklemeden kavramsal olarak farklı değildir, ancak kolayca gözden kaçabileceği şekilde listelenmiştir. Örneğin , aynı gösterimi kullanarak ints ve doublelere ekleyebilirsiniz x += 2ve derleyici şunu üretir:

  • türe özgü CPU talimatları
  • aynı türden bir sonuç.

Aşırı yükleme, daha sonra sorunsuz bir şekilde kullanıcı tanımlı türleri kapsar:

std::string x;
int y = 0;

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

Temel türler için derleyici tarafından sağlanan aşırı yüklemeler, yüksek seviyeli (3GL +) bilgisayar dillerinde yaygındır ve çok biçimliliğin açık tartışılması genellikle daha fazlasını ifade eder. (2GL'ler - montaj dilleri - genellikle programcının farklı türler için farklı anımsatıcıları açıkça kullanmasını gerektirir.)

> Standart dönüşümler

C ++ Standardının dördüncü bölümü, Standart dönüştürmeleri açıklar.

İlk nokta güzel bir şekilde özetliyor (eski bir taslaktan - umarım hala büyük ölçüde doğrudur):

-1- Standart dönüştürmeler, yerleşik türler için tanımlanan örtük dönüştürmelerdir. Clause, bu tür dönüşümlerin tam kümesini numaralandırır. Standart bir dönüştürme dizisi, aşağıdaki sırayla bir standart dönüştürme dizisidir:

  • Aşağıdaki kümeden sıfır veya bir dönüşüm: lvalue-to-rvalue dönüşümü, diziden işaretçiye dönüşüm ve fonksiyondan işaretçiye dönüşüm.

  • Aşağıdaki kümeden sıfır veya bir dönüşüm: integral promosyonlar, kayan nokta promosyonu, integral dönüşümler, kayan nokta dönüşümleri, kayan integral dönüşümleri, işaretçi dönüşümleri, işaretçiden üyeye dönüşümleri ve boole dönüşümleri.

  • Sıfır veya bir yeterlilik dönüşümü.

[Not: standart bir dönüştürme dizisi boş olabilir, yani dönüştürme içermeyebilir. ] Bir ifadeyi gerekli bir hedef türüne dönüştürmek gerekirse, standart bir dönüştürme dizisi uygulanacaktır.

Bu dönüşümler aşağıdaki gibi koda izin verir:

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

a(3.14);
a(42);

Önceki testi uygulamak:

Polimorfik olmak için, [ a()] en az iki farklı türdeki değerlerle (örn. intVe double), türe uygun kodu bulup çalıştırarak çalışabilmelidir .

a()kendisi için kod çalıştırır doubleve bu nedenle polimorfik değildir .

Ama, ikinci çağrısında a()derleyici dönüştürmek için bir "kayan noktalı promosyon" (Standart §4) için tip-uygun kodu oluşturmak için bildiği 42için 42.0. Bu ekstra kod çağıran işlevdedir. Sonuç kısmında bunun önemini tartışacağız.

> Zorlama, yayınlar, örtük kurucular

Bu mekanizmalar, kullanıcı tanımlı sınıfların yerleşik türlerin Standart dönüştürmelerine benzer davranışları belirtmesine izin verir. Bir bakalım:

int a, b;

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

Burada nesne std::cin, bir dönüştürme operatörü yardımıyla boole bağlamında değerlendirilir. Bu kavramsal olarak, yukarıdaki konudaki Standart dönüşümlerden "entegre promosyonlar" ve diğerleri ile gruplandırılabilir.

Örtük oluşturucular aynı şeyi etkili bir şekilde yapar, ancak dönüştürme türü tarafından kontrol edilir:

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

Derleyici tarafından sağlanan aşırı yüklemelerin, dönüştürmelerin ve zorlamanın etkileri

Düşünmek:

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

Miktarın xbölme sırasında gerçek bir sayı olarak ele alınmasını istiyorsak (yani 6'ya yuvarlamak yerine 6,5 olması), sadece olarak değiştirmemiz gerekir typedef double Amount.

Bu güzel, ancak kodu açıkça "türü doğru" yapmak çok fazla iş olmazdı :

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

Ancak, ilk sürümü aşağıdakilere dönüştürebileceğimizi düşünün template:

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

Bu küçük "kolaylık özellikleri" sayesinde, ya intda ya da doubleamaçlandığı gibi kolayca somutlaştırılabilir ve çalışabilir. Bu özellikler olmadan, açık yayınlara, yazım özelliklerine ve / veya politika sınıflarına, bazı ayrıntılı, hataya açık karışıklığa ihtiyacımız olurdu:

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

Bu nedenle, yerleşik türler için derleyici tarafından sağlanan operatör aşırı yüklemesi, Standart dönüştürmeler, çevrim / zorlama / örtük oluşturucular - hepsi çok biçimlilik için ince bir desteğe katkıda bulunur. Bu cevabın üst kısmındaki tanımdan, eşleştirerek "türe uygun kodu bulma ve yürütme" yi ele alırlar:

  • parametre türlerinden "uzakta"

    • dan birçok veri tipleri algoritmik kod kolları polimorfik

    • için (aynı veya farklı) tip bir (potansiyel olarak daha az) numarası için yazılmış kod.

  • Sabit tip değerlerinden "ila" parametrik türler

Onlar do değil kendileri tarafından polimorfik bağlamları kurmak, ancak bu tür içerikleri içinde gücünü verir / basitleştirmek kodu yok.

Aldatıldığını hissedebilirsin ... pek görünmüyor. Buradaki önemli nokta, parametrik polimorfik bağlamlarda (yani şablonların veya makroların içinde), keyfi olarak geniş bir tür yelpazesini desteklemeye çalışıyoruz, ancak genellikle bunlar üzerindeki işlemleri, bir küçük türler. İşlem / değer mantıksal olarak aynı olduğunda, neredeyse aynı işlevler veya tür başına veri oluşturma ihtiyacını azaltır. Bu özellikler, "en iyi çaba" tutumunu eklemek, sınırlı mevcut işlevleri ve verileri kullanarak sezgisel olarak beklenenleri yapmak ve yalnızca gerçek belirsizlik olduğunda bir hatayla durmak için işbirliği yapar.

Bu, polimorfik kodu destekleyen polimorfik koda olan ihtiyacı sınırlandırmaya yardımcı olur, polimorfizmin kullanımı etrafında daha sıkı bir ağ çizer, böylece yerelleştirilmiş kullanım yaygın kullanımı zorlamaz ve polimorfizmin faydalarını gerektiğinde uygulamaya koyma maliyetini empoze etmeden kullanılabilir hale getirir. derleme zamanı, kullanılan türleri desteklemek için nesne kodunda aynı mantıksal işlevin birden çok kopyasına sahip olmak ve satır içi veya en azından derleme zamanı çözümlenmiş çağrıların aksine sanal gönderme yapmak. C ++ 'da tipik olduğu gibi, programcıya polimorfizmin kullanıldığı sınırları kontrol etme özgürlüğü verilir.


1
-1 Terminoloji tartışması dışında harika yanıt. C ++ standardı , §1.8 / 1'de "polimorfik" terimini tanımlar , burada sanal işlevlerle ilgili bölüm 10.3'e atıfta bulunulur. Yani hiçbir kıpır kıpır oda, tartışmaya yer yok, kişisel görüşe yer yok: standart C ++ bağlamında bu terim bir kez ve herkes için tanımlanır. Ve pratikte bir rol oynar. Örneğin, §5.2.7 / 6 dynamic_cast, "bir polimorfik tipte bir işaretçi veya bir değer" gerektirir. Şerefe & hth.,
Şerefe ve hth. - Alf

@Alf: harika referans - bakış açınızın çok dar olduğunu düşünüyorum. Aşırı yükleme, geçici ve parametrik polimorfizm vb. Listeleyen sorulardan, bir yanıtın C ++ 'nın yeteneklerini genel Comp ile ilişkilendirmesi gerektiği çok açıktır. Sci. terimlerin anlamı. Aslında, Stroustrup'un sözlüğü "çok biçimlilik - farklı türlerdeki varlıklara tek bir arabirim sağlar. Sanal işlevler, bir temel sınıf tarafından sağlanan bir arabirim aracılığıyla dinamik (çalışma zamanı) polimorfizm sağlar. Aşırı yüklenmiş işlevler ve şablonlar, statik (derleme zamanı) polimorfizm" sağlar. TC ++ PL 12.2.6, 13.6.1, D&E 2.9. "
Tony Delroy

@Tony: Cevabınızın asıl amacı yanlış değil. tamam, harika. sadece bu yanlış. Terimlerden geriye doğru anladınız: Resmi akademik terminoloji, Kutsal Uluslararası Standart tarafından tanımlanan dar bir terimdir ve insanların biraz farklı şeyler ifade edebildiği resmi olmayan kaba terminoloji, esas olarak bu soru ve cevapta kullanılan terimdir. Şerefe & hth.,
Şerefe ve hth. - Alf

@Alf: Cevabın harika olmasını diliyorum - "Diğer mekanizmaların" satırların beşte birinde yeniden yazılması gerekiyor ve ben polimorfik mekanizmaların zıttı daha somut özellikler-ve-çıkarımlar üzerinde düşünüyorum / tasarlıyorum. Her neyse, benim anladığım kadarıyla resmi akademik yalnızca-C ++ odaklı anlam dar olabilir, ancak resmi akademik genel Comp. Sci. anlam, Stroustrup'un sözlüğünde kanıtlandığı gibi değildir. Kesin bir şeye ihtiyacımız var - örneğin Knuth'tan tanım - henüz şans yok. Bir C ++ gurusu olduğunuz için minnettarım, ancak özellikle bununla ilgili ilgili kanıtlara işaret edebilir misiniz?
Tony Delroy

1
@Alf: İkincisi, polimorfizmi olduğunu eminim resmen herhangi nezih genel Comp tanımlanan. Sci. kullanımımla (ve Stroustrup'larla) uyumlu (zamansız, kararlı) bir şekilde kitap. Wikipedia makalesi, onu bu şekilde tanımlayan birkaç akademik yayını birbirine bağlar: "Polimorfik işlevler, işlenenleri (gerçek parametreleri) birden fazla türe sahip olabilen işlevlerdir. Polimorfik türler, işlemleri birden fazla türdeki değerlere uygulanabilen türlerdir." ( lucacardelli.name/Papers/OnUnderstanding.A4.pdf adresinden ). Öyleyse, soru "Comp. Sci için kim konuşuyor" ...?
Tony Delroy

15

C ++ 'da, önemli ayrım çalışma zamanı ile derleme zamanı bağlamadır. Ad-hoc ve parametrik, daha sonra açıklayacağım gibi gerçekten yardımcı olmuyor.

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

Not - çalışma zamanı polimorfizmi hala derleme zamanında çözülebilir, ancak bu sadece optimizasyondur. Çalışma zamanı çözümünü verimli bir şekilde desteklemeye ihtiyaç duymak ve diğer sorunlara karşı ticaret yapmak, sanal işlevlerin oldukları gibi olmasına yol açan şeyin bir parçasıdır. Ve bu, C ++ 'daki tüm polimorfizm biçimleri için gerçekten anahtardır - her biri farklı bir bağlamda yapılan farklı takas kümelerinden kaynaklanmaktadır.

Fonksiyon aşırı yükleme ve operatör aşırı yükleme, önemli olan her şekilde aynı şeydir. Bunları kullanmak için isimler ve sözdizimi çok biçimliliği etkilemez.

Şablonlar, aynı anda çok sayıda işlev aşırı yüklemesi belirtmenize izin verir.

Aynı çözüm süresi fikri için başka bir isim grubu daha var ...

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

Bu isimler OOP ile daha çok ilişkilidir, bu nedenle bir şablonun veya üye olmayan başka bir işlevin erken bağlamayı kullandığını söylemek biraz tuhaftır.

Sanal işlevler ile işlev aşırı yükleme arasındaki ilişkiyi daha iyi anlamak için, "tek gönderim" ve "çoklu gönderim" arasındaki farkı anlamak da yararlıdır. Fikir bir ilerleme olarak anlaşılabilir ...

  • İlk olarak, monomorfik fonksiyonlar vardır. İşlevin uygulaması benzersiz bir şekilde işlev adıyla tanımlanır. Parametrelerin hiçbiri özel değil.
  • Sonra, tek gönderim var. Parametrelerden biri özel kabul edilir ve (adla birlikte) hangi uygulamanın kullanılacağını belirlemek için kullanılır. OOP'de, bu parametreyi "nesne" olarak düşünme, işlev adından önce listeleme vb. Eğilimindeyiz.
  • Ardından, çoklu gönderim var. Tüm parametreler, hangi uygulamanın kullanılacağının belirlenmesine katkıda bulunur. Bu nedenle, bir kez daha, parametrelerin hiçbirinin özel olmasına gerek yoktur.

Açık bir şekilde OOP'de bir parametreyi özel olarak belirtmek için bir bahaneden daha fazlası var, ancak bu onun bir parçası. Ve değiş tokuşlar hakkında söylediklerime geri dönersek - tek gönderimi verimli bir şekilde yapmak oldukça kolaydır (olağan uygulamaya "sanal tablolar" denir). Birden çok gönderim, yalnızca verimlilik açısından değil, aynı zamanda ayrı derleme açısından da daha zordur. Merak ediyorsanız, "ifade problemine" bakabilirsiniz.

Üye olmayan işlevler için "erken bağlama" terimini kullanmak biraz tuhaf olduğu gibi, polimorfizmin derleme zamanında çözüldüğü "tek gönderim" ve "çoklu gönderim" terimlerini kullanmak biraz tuhaftır. Genellikle, C ++ 'nın birden çok gönderime sahip olmadığı düşünülür ve bu, belirli bir tür çalışma zamanı çözümü olarak kabul edilir. Bununla birlikte, işlev aşırı yüklemesi, derleme zamanında yapılan çoklu gönderim olarak görülebilir.

Parametrik ve geçici polimorfizme geri dönersek, bu terimler işlevsel programlamada daha popülerdir ve C ++ 'da pek işe yaramazlar. Yine de...

Parametrik polimorfizm, parametre olarak türlere sahip olduğunuz ve bu parametreler için hangi türü kullandığınıza bakılmaksızın tam olarak aynı kodun kullanıldığı anlamına gelir.

Ad-hoc polimorfizm, belirli türlere bağlı olarak farklı kodlar sağlamanız anlamında geçici bir durumdur.

Aşırı yükleme ve sanal işlevlerin her ikisi de geçici polimorfizm örnekleridir.

Yine, bazı eş anlamlılar var ...

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

Bunların tam olarak eşanlamlı olmaması dışında, genellikle öyleymiş gibi ele alınsalar da, C ++ 'da kafa karışıklığının ortaya çıkması muhtemeldir.

Bunları eşanlamlı olarak ele almanın arkasındaki mantık, polimorfizmi belirli tür sınıflarıyla sınırlayarak, bu tür sınıflarına özgü işlemleri kullanmanın mümkün hale gelmesidir. Buradaki "sınıflar" kelimesi OOP anlamında yorumlanabilir, ancak gerçekten sadece belirli işlemleri paylaşan (genellikle adlandırılmış) tür kümelerine atıfta bulunur.

Bu nedenle, kısıtlanmamış polimorfizmi ifade etmek için parametrik polimorfizm genellikle (en azından varsayılan olarak) alınır. Tür parametrelerine bakılmaksızın aynı kod kullanıldığından, yalnızca desteklenebilir işlemler tüm türler için işe yarayan işlemlerdir. Tür kümesini sınırlandırmadan bırakarak, bu türlere uygulayabileceğiniz işlem kümesini ciddi şekilde sınırlamış olursunuz.

Örneğin Haskell'de, sahip olabilirsiniz ...

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

aBurada kısıtsız polimorfik türüdür. Herhangi bir şey olabilir, bu nedenle bu türden değerlerle yapabileceğimiz pek bir şey yok.

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

Burada, sınıfın abir üyesi olmak Numsınırlıdır - sayılar gibi davranan türler. Bu kısıtlama, bu değerlerle sayıya benzer şeyler yapmanızı, örneğin onları eklemenizi sağlar. Hatta 3polimorfik - tipi çıkarsama rakamları dışarı anlamına geldiğini 3Çeşidi a.

Bunu kısıtlı parametrik polimorfizm olarak düşünüyorum. Yalnızca bir uygulama vardır, ancak yalnızca kısıtlı durumlarda uygulanabilir. Geçici yönü, hangisinin +ve hangisinin 3kullanılacağının seçimidir . Her "örneğinin" Numkendi farklı uygulaması vardır. Yani Haskell'de bile "parametrik" ve "kısıtlanmamış" gerçekten eşanlamlı değil - beni suçlamayın, bu benim hatam değil!

C ++ 'da, hem aşırı yükleme hem de sanal işlevler geçici polimorfizmdir. Geçici polimorfizmin tanımı, uygulamanın çalışma zamanında mı yoksa derleme zamanında mı seçildiğini umursamaz.

Her şablon parametresinin türü varsa, C ++ şablonlarla parametrik polimorfizme çok yaklaşır typename. Tür parametreleri vardır ve hangi tür kullanılırsa kullanılsın tek bir uygulama vardır. Bununla birlikte, "İkame Başarısızlığı Bir Hata Değildir" kuralı, şablon içindeki işlemlerin kullanılması sonucunda örtük kısıtlamaların ortaya çıktığı anlamına gelir. Ek zorluklar arasında, alternatif şablonlar sağlamak için şablon uzmanlığı bulunur - farklı (geçici) uygulamalar.

Yani bir bakıma C ++ parametrik polimorfizme sahiptir, ancak dolaylı olarak kısıtlanmıştır ve geçici alternatifler tarafından geçersiz kılınabilir - yani bu sınıflandırma C ++ için gerçekten işe yaramaz.


+1 Pek çok ilginç nokta ve fikir. Haskell hakkında okumak için sadece birkaç saat harcadım, bu yüzden " aburada kısıtlanmamış bir polimorfik tür [...] bu yüzden bu tür değerlerle yapabileceğimiz pek bir şey yok." ilgi çekiciydi - C ++ sans Kavramlarında, yalnızca bir şablon parametresi olarak belirtilen türde bir bağımsız değişken üzerinde belirli bir işlem kümesini denemekle sınırlı değilsiniz ... boost kavramları gibi kitaplıklar diğer şekilde çalışır - türün işlemleri desteklediğinden emin olur ek işlemlerin kazara kullanımına karşı korunmak yerine belirtirsiniz.
Tony Delroy

@Tony - Kavramlar, şablonların çok biçimliliğini açıkça sınırlamanın bir yoludur. Örtük kısıtlamalar açıkça uyumluluk nedeniyle ortadan kalkmayacaktır, ancak açık kısıtlamalar kesinlikle işleri önemli ölçüde iyileştirecektir. Kavramlara yönelik geçmiş planların bir şekilde Haskell tip sınıflarıyla ilgili olduğundan oldukça eminim, ancak bunlara o kadar derinlemesine bakmadım ve en son "sığ" baktığımda Haskell'i pek bilmiyordum.
Steve314

"Örtük kısıtlamalar açıkça uyumluluk nedeniyle ortadan kalkmayacak" - bellekten, C ++ 0x Kavramları (vaat ediyor: - /) "örtük kısıtlamaları" engelledi - türü yalnızca Kavramlar tarafından vaat edilen şekillerde kullanabilirsiniz.
Tony Delroy

2

Ad-hoc polimorfizmine gelince, bu, işlevin aşırı yüklenmesi veya operatörün aşırı yüklenmesi anlamına gelir. Buraya göz atın:

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

Parametrik polimorfizm ile ilgili olarak, şablon fonksiyonları da sayılabilir çünkü SABİT tiplerin parametrelerini almaları gerekmez. Örneğin, bir işlev tamsayı dizisini sıralayabilir ve ayrıca dizeler dizisini vb. Sıralayabilir.

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


1
Ne yazık ki, doğru olmasına rağmen, bu yanıltıcıdır. Şablon işlevleri, SFINAE kuralı nedeniyle örtük kısıtlamalar alabilir - şablon içindeki bir işlemin kullanılması, polimorfizmi örtük olarak sınırlar - ve şablon uzmanlığı, daha genel şablonları geçersiz kılan geçici alternatif şablonlar sağlayabilir. Dolayısıyla, bir şablon (varsayılan olarak) sınırlandırılmamış parametrik polimorfizm sağlar, ancak bunun bir zorunluluğu yoktur - kısıtlı veya geçici olmanın en az iki yolu vardır.
Steve314

Aslında sizin örneğiniz - sıralama - bir kısıtlama anlamına gelir. Sıralama yalnızca sıralanan türler için işe yarar (yani <ve benzer işleçleri sağlayın ). Haskell'de, bu gereksinimi sınıfı kullanarak açıkça ifade edersiniz Ord. <Belirli bir türe bağlı olarak (örneğinin sağladığı şekilde) farklı bir şey elde etmeniz , Ordgeçici polimorfizm olarak kabul edilecektir.
Steve314

2

Bunun herhangi bir faydası olmayabilir, ancak bunu arkadaşlarımı programlamaya tanıtmak için ana işlev gibi tanımlanmış işlevler vererek STARTve ENDböylece çok göz korkutucu olmamak için yaptım (sadece main.cpp dosyasını kullandılar). Polimorfik sınıflar ve yapılar, şablonlar, vektörler, diziler, önişlemci yönergeleri, arkadaşlık, işleçler ve işaretçiler içerir (bunların tümünü muhtemelen çok biçimlilik denemeden önce bilmeniz gerekir):

Not: Bitmedi, ancak fikir edinebilirsiniz

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

İşte Polimorfik sınıfların kullanıldığı temel bir örnek

#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

Polimorfizm, bir operatörün farklı durumlarda farklı davranması için kullanıldığı birçok form anlamına gelir. Çok biçimlilik, kalıtımı uygulamak için kullanılır. Örneğin, bir sınıf şekli için bir fn draw () tanımladık, ardından fn çizimi daire, kutu, üçgen ve diğer şekiller için uygulanabilir. (sınıf şeklinin nesneleridir)


-3

Bu insanlara KES derse

The Surgeon
The Hair Stylist
The Actor

Ne olacak?

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.

Yukarıdaki gösterim, OOP'de polimorfizmin (aynı isim, farklı davranış) ne olduğunu gösterir.

Bir görüşmeye gidecekseniz ve görüşmeci, oturduğumuz aynı odada polimorfizm için canlı bir örnek anlatmanızı / göstermenizi isterse, diyelim ki-

Cevap - Kapı / Pencereler

Nasıl mı merak ediyorsunuz?

Kapıdan / Pencereden - bir kişi gelebilir, hava gelebilir, ışık gelebilir, yağmur gelebilir vb.

yani Biri farklı davranış oluşturur (Polimorfizm).

Daha iyi ve basit bir şekilde anlamak için yukarıdaki örneği kullandım .. Kod için referansa ihtiyacınız varsa yukarıdaki cevapları takip edin.


C ++ 'daki Polimorfizmi daha iyi anlamak için bahsettiğim gibi, yukarıdaki örneği kullandım. Bu, daha taze bir kişinin görüşme sırasında kodun arkasında ne olduğunu veya anlamını gerçekten anlamasına ve ilişkilendirmesine yardımcı olabilir. Teşekkür ederim!
Sanchit

op "c ++ 'da çok biçimlilik" diye sordu. cevabınız çok soyut.
StahlRat
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.