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 x
ve y
girdi olarak verilmektedir .
Polimorfizm sergilemek f()
için, en az iki farklı türdeki değerlerle (örneğin int
ve 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:
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 int
s ve double
lere ekleyebilirsiniz x += 2
ve 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. int
Ve double
), türe uygun kodu bulup çalıştırarak çalışabilmelidir .
a()
kendisi için kod çalıştırır double
ve 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 42
iç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 x
bö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 int
da ya da double
amaç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:
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.