Dinamik polimorfizmi önlemek için CRTP


Yanıtlar:


142

İki yol var.

İlki, türlerin yapısı için arayüzü statik olarak belirtmektir:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo(); // required to compile.
};

struct your_type : base<your_type> {
  void foo(); // required to compile.
};

İkincisi, referanstan-tabana veya işaretçi-tabana deyiminin kullanımından kaçınmak ve kablolamayı derleme zamanında yapmaktır. Yukarıdaki tanımı kullanarak, aşağıdakilere benzeyen şablon işlevlerine sahip olabilirsiniz:

template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
  obj.foo(); // will do static dispatch
}

struct not_derived_from_base { }; // notice, not derived from base

// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload

Dolayısıyla, işlevlerinizde yapı / arabirim tanımını ve derleme zamanı türü çıkarımını birleştirmek, dinamik gönderim yerine statik dağıtım yapmanızı sağlar. Statik polimorfizmin özü budur.


5
Bunu vurgulamak istiyorum not_derived_from_basetüretilmiş değildir base, ne de türemiş olduğunu base...
leftaroundabout

3
Aslında, my_type / your_type içinde foo () beyanı gerekli değildir. codepad.org/ylpEm1up (Yığın taşmasına neden olur) - Derleme sırasında foo tanımını zorlamanın bir yolu var mı? - Tamam, bir çözüm buldum: ideone.com/C6Oz9 - Belki cevabınızda bunu düzeltmek istersiniz.
cooky451

3
Bu örnekte CRTP kullanmanın motivasyonunun ne olduğunu bana açıklayabilir misiniz? Bar şablon <class T> void bar (T & obj) {obj.foo (); }, o zaman foo sağlayan herhangi bir sınıf iyi olur. Dolayısıyla, örneğinize göre, CRTP'nin tek kullanımının derleme zamanında arayüzü belirtmek olduğu görülüyor. Bunun için mi?
Anton Daneyko

1
@Dean Michael Gerçekten de örnekteki kod, my_type ve your_type'da foo tanımlanmamış olsa bile derlenir. Bunlar geçersiz kılmalar olmadan base :: foo özyinelemeli olarak çağrılır (ve stackoverflows). Yani belki de cooky451'in gösterdiği gibi cevabınızı düzeltmek istersiniz?
Anton Daneyko

@mezhaka: Evet, Dean Michael'ın örneği eksik çünkü sizin de gösterdiğiniz gibi CRTP olmadan daha kısaca uygulanabilir. Ancak bir template<class T> bar(base2<T> &obj) { obj.quux(); }- yani farklı bir bar()uygulamaya sahip ikinci bir temel sınıf - ekleyin ve CRTP'nin faydası ortaya çıkar.
Nemo

18

Ben de CRTP ile ilgili düzgün tartışmalar arıyordum. Todd Veldhuizen'in Techniques for Scientific C ++ , bu (1.3) ve ifade şablonları gibi diğer birçok gelişmiş teknik için harika bir kaynaktır.

Ayrıca, Coplien'in orijinal C ++ Gems makalesinin çoğunu Google Books'ta okuyabileceğinizi öğrendim. Belki hala durum budur.


@fizzer Önerdiğiniz bölümü okudum, ancak şablonun <class T_leaftype> double sum (Matrix <T_leaftype> & A) ne işe yaradığını hala anlamadım; <class Whatever> çift toplamına kıyasla sizi satın alır (Whatever & A);
Anton Daneyko

@AntonDaneyko Bir temel örnekte çağrıldığında, temel sınıfın toplamı, örneğin bir kare gibi varsayılan uygulamayla "bir şeklin alanı" olarak adlandırılır. Bu durumda CRTP'nin amacı, türetilmiş davranış gerekene kadar yamuğa şekil olarak başvurabilirken, en çok türetilen uygulamayı, "yamuğun alanını" vs. çözmektir. Temel olarak, normalde ihtiyacınız olduğunda dynamic_castveya sanal yöntemlerle.
John P

1

CRTP'ye bakmam gerekiyordu . Ancak bunu yaptıktan sonra Statik Polimorfizm hakkında bazı şeyler buldum . Sorunuzun cevabının bu olduğundan şüpheleniyorum.

ATL'nin bu kalıbı oldukça yoğun bir şekilde kullandığı ortaya çıktı .


-5

Bu Wikipedia cevabı ihtiyacınız olan her şeye sahiptir. Yani:

template <class Derived> struct Base
{
    void interface()
    {
        // ...
        static_cast<Derived*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        Derived::static_sub_func();
        // ...
    }
};

struct Derived : Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

Bunun seni ne kadar satın aldığını bilmeme rağmen. Bir sanal işlev çağrısının ek yükü (derleyiciye bağlıdır, elbette):

  • Bellek: Her sanal işlev için bir işlev işaretçisi
  • Çalışma zamanı: Bir işlev işaretçisi çağrısı

CRTP statik polimorfizminin ek yükü:

  • Bellek: Şablon somutlaştırması başına Base çoğaltması
  • Çalışma zamanı: Bir işlev işaretçi çağrısı + static_cast ne yapıyorsa

4
Aslında, şablon somutlaştırması başına Base'in çoğaltılması bir yanılsamadır, çünkü (hala bir vtable'ınız yoksa) derleyici, temelin ve türetilmiş olanın depolanmasını sizin için tek bir yapı halinde birleştirecektir. İşlev işaretçisi çağrısı ayrıca derleyici (static_cast bölümü) tarafından optimize edilir.
Dean Michael

19
Bu arada, CRTP analiziniz yanlış. Olmalı: Bellek: Dean Michael'ın dediği gibi hiçbir şey. Çalışma Zamanı: Bir (daha hızlı) statik işlev çağrısı, sanal değil, alıştırmanın tüm amacı budur. static_cast hiçbir şey yapmaz, sadece kodun derlenmesine izin verir.
Frederik Slijkerman

2
Demek istediğim, temel kodun tüm şablon örneklerinde kopyalanacağıdır (sözünü ettiğin en kaynaşma). Şablon parametresine dayanan tek bir yönteme sahip bir şablona benzer; diğer her şey bir temel sınıfta daha iyidir, aksi takdirde birden çok kez içeri alınır ('birleştirilir').
user23167

1
Tabandaki her yöntem , türetilen her biri için yeniden derlenecektir. Örneklenen her yöntemin farklı olduğu (beklenen) durumda (Türetilmiş özelliklerin farklı olması nedeniyle), bu zorunlu olarak ek yük olarak sayılamaz. Ancak, (normal) temel sınıftaki karmaşık bir yöntemin alt sınıfların sanal yöntemlerini çağırdığı duruma kıyasla daha büyük genel kod boyutuna yol açabilir. Ayrıca, Base <Derived> 'e gerçekte hiçbir şekilde <Derived>' e bağlı olmayan yardımcı program yöntemlerini koyarsanız, yine de somutlaştırılacaktır. Belki küresel optimizasyon bunu bir şekilde düzeltir.
greggo

CRTP'nin birkaç katmanından geçen bir çağrı, derleme sırasında bellekte genişler, ancak TCO ve satır içi aracılığıyla kolayca daralabilir. O halde CRTP'nin kendisi gerçekten suçlu değil, değil mi?
John P
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.