Örtük ve açık arabirimler


9

Derleme zamanı polimorfizminin ve çalışma zamanı polimorfizminin gerçek sınırlamalarını anladığımı düşünüyorum. Ancak açık arayüzler (çalışma zamanı polimorfizmi, yani sanal fonksiyonlar ve işaretçiler / referanslar) ve örtülü arayüzler (derleme zamanı polimorfizmi, yani şablonlar) arasındaki kavramsal farklar nelerdir .

Düşüncelerim, aynı açık arabirimi sunan iki nesnenin aynı türde nesne (veya ortak bir ataya sahip) olması gerektiğinden, aynı örtülü arabirimi sunan iki nesnenin aynı tür nesne olması ve örtük hariç tutulması gerektiğidir. Her ikisinin de sunduğu arayüz, oldukça farklı bir işleve sahip olabilir.

Bunun hakkında bir fikrin var mı?

Ve eğer iki nesne aynı örtük arayüzü sunuyorsa, hangi nedenlerin (sanal işlev arama tablosu olmadan dinamik gönderime ihtiyaç duyulmamasının teknik faydasının yanında), bu nesnelerin o arayüzü bildiren bir temel nesneden miras almaması nedeniyle vardır, onu açık bir arayüz yapıyor mu? Bunu söylemenin başka bir yolu: bana, aynı örtük arabirimi sunan iki nesnenin (ve bu nedenle örnek şablon sınıfına tür olarak kullanılabileceği), bu arabirimi açık yapan bir temel sınıftan miras almaması gerektiğini söyleyebilir misiniz?

İlgili bazı yayınlar:


İşte bu soruyu daha somut hale getirecek bir örnek:

Örtülü Arayüz:

class Class1
{
public:
  void interfaceFunc();
  void otherFunc1();
};

class Class2
{
public:
  void interfaceFunc();
  void otherFunc2();
};

template <typename T>
class UseClass
{
public:
  void run(T & obj)
  {
    obj.interfaceFunc();
  }
};

Açık Arayüz:

class InterfaceClass
{
public:
  virtual void interfaceFunc() = 0;
};

class Class1 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc1();
};

class Class2 : public InterfaceClass
{
public:
  virtual void interfaceFunc();
  void otherFunc2();
};

class UseClass
{
public:
  void run(InterfaceClass & obj)
  {
    obj.interfaceFunc();
  }
};

Daha derinlemesine, somut bir örnek:

Bazı C ++ sorunları şunlardan biri ile çözülebilir:

  1. şablon türü örtük bir arabirim sağlayan şablonlu bir sınıf
  2. müstehcen bir arabirim sağlayan temel sınıf işaretçisini alan şablonsuz bir sınıf

Değişmeyen kod:

class CoolClass
{
public:
  virtual void doSomethingCool() = 0;
  virtual void worthless() = 0;
};

class CoolA : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that an A would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

class CoolB : public CoolClass
{
public:
  virtual void doSomethingCool()
  { /* Do cool stuff that a B would do */ }

  virtual void worthless()
  { /* Worthless, but must be implemented */ }
};

Durum 1 . Açık bir arabirim sağlayan temel sınıf işaretçisini alan şablonsuz bir sınıf:

class CoolClassUser
{
public:  
  void useCoolClass(CoolClass * coolClass)
  { coolClass.doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Durum 2 . Şablon türü örtük bir arabirim sağlayan şablonlanmış bir sınıf:

template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  CoolA * c1 = new CoolClass;
  CoolB * c2 = new CoolClass;

  CoolClassUser<CoolClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Durum 3 . Şablon türü örtük bir arabirim sağlayan şablonlanmış bir sınıf (bu sefer, şu kaynaktan türetilmez CoolClass:

class RandomClass
{
public:
  void doSomethingCool()
  { /* Do cool stuff that a RandomClass would do */ }

  // I don't have to implement worthless()! Na na na na na!
}


template <typename T>
class CoolClassUser
{
public:  
  void useCoolClass(T * coolClass)
  { coolClass->doSomethingCool(); }
};

int main()
{
  RandomClass * c1 = new RandomClass;
  RandomClass * c2 = new RandomClass;

  CoolClassUser<RandomClass> user;
  user.useCoolClass(c1);
  user.useCoolClass(c2);

  return 0;
}

Durum 1, aktarılan nesnenin useCoolClass()bir alt öğesi CoolClass(ve uygulama worthless()) olmasını gerektirir. Olgu 2 ve 3 ise işlevi olan herhangi bir sınıfı alacaktır doSomethingCool().

Kod kullanıcıları her zaman iyi alt sınıflar olsaydı CoolClass, Durum 1 sezgisel mantıklıdır, çünkü CoolClassUserher zaman bir a uygulamasını beklerdi CoolClass. Ancak bu kodun bir API çerçevesinin parçası olacağını varsayalım, bu yüzden kullanıcıların CoolClassbir doSomethingCool()işlevi olan kendi sınıflarını alt sınıflara mı yoksa yuvarlamak isteyip istemediklerini tahmin edemem .


Belki bir şey eksik, ama ilk paragrafta zaten önemli bir fark açıkça belirtilmemiş
Robert Harvey

2
Soyut bir Sınıfa işaretçi alan bir Sınıf veya fonksiyona sahip olan (açık bir arayüz sağlayan) veya örtük bir arayüz sağlayan bir nesneyi kullanan şablonlanmış bir Sınıf veya fonksiyona sahip olarak çözülebilecek bazı problemler vardır. Her iki çözüm de işe yarıyor. İlk çözümü ne zaman kullanmak istersiniz? İkinci?
Chris Morris

kavramları biraz daha açtığınızda bu hususların çoğunun parçalandığını düşünüyorum. örneğin, kalıtımsal olmayan statik polimorfizmi nereye sığdırırsınız?
Javier

Yanıtlar:


8

Önemli noktayı zaten tanımladınız - biri çalışma zamanı ve diğeri derleme zamanı . İhtiyacınız olan gerçek bilgi, bu seçimin sonuçlarıdır.

Derleme zamanı:

  • Pro: Derleme zamanı arabirimleri, çalışma zamanı arabirimlerinden çok daha ayrıntılıdır. Bununla demek istediğim, onları çağırırken yalnızca tek bir işlevin veya bir dizi işlevin gereksinimlerini kullanabilmenizdir. Her zaman tüm arayüzü yapmak zorunda değilsiniz. Gereksinimler sadece ve tam olarak ihtiyacınız olan şeydir.
  • Pro: CRTP gibi teknikler, işleçler gibi şeylerin varsayılan uygulamalarını uygulamak için örtülü arabirimler kullanabileceğiniz anlamına gelir. Çalışma zamanı kalıtımıyla böyle bir şey yapamazsınız.
  • Pro: Örtük arabirimlerin oluşturulması ve "devralınması" ile çalışma zamanı arabirimlerinden çok daha kolaydır ve herhangi bir ikili kısıtlama getirmezler - örneğin, POD sınıfları örtük arabirimler kullanabilir. virtualÖrtülü arayüzlere sahip miras veya diğer maskaralıklara gerek yoktur - büyük bir avantaj.
  • Pro: Derleyici, derleme zamanı arabirimleri için daha fazla optimizasyon yapabilir. Ek olarak, ekstra tip güvenlik daha güvenli kod sağlar.
  • Pro: Son nesnenin boyutunu veya hizalamasını bilmediğiniz için çalışma zamanı arabirimleri için değer yazma yapmak imkansızdır. Bu, değer yazarak ihtiyaç duyan / fayda sağlayan her durumun şablonlardan büyük faydalar elde ettiği anlamına gelir.
  • Con: Şablonlar derlemek ve kullanmak için bir orospu ve derleyiciler arasında fiddly taşıma olabilir
  • Con: Şablonlar çalışma zamanında yüklenemez (açıkçası), bu nedenle örneğin dinamik veri yapılarını ifade etme sınırları vardır.

Çalışma süresi:

  • Pro: Son tür çalışma zamanına kadar karar vermek zorunda değil. Bu, çalışma zamanı kalıtımının, şablonlar bunu yapabiliyorsa, bazı veri yapılarını çok daha kolay ifade edebileceği anlamına gelir. Ayrıca, çalışma zamanı polimorfik türlerini C sınırları boyunca, örneğin COM'da dışa aktarabilirsiniz.
  • Pro: Çalışma zamanı kalıtımını belirtmek ve uygulamak çok daha kolaydır ve derleyiciye özgü herhangi bir davranış elde edemezsiniz.
  • Con: Çalışma zamanı kalıtım, derleme zamanı kalıtımdan daha yavaş olabilir.
  • Con: Çalışma zamanı devralma türü bilgilerini kaybeder.
  • Con: Çalışma zamanı kalıtım çok daha az esnektir.
  • Con: Birden fazla miras bir orospu.

Göreli liste göz önüne alındığında, çalışma zamanı kalıtımının belirli bir avantajına ihtiyacınız yoksa, listeyi kullanmayın. Şablonlardan daha yavaş, daha az esnek ve daha az güvenlidir.

Düzenleme: C ++ özellikle miras için kullanımları vardır belirterek It yetmeyecek diğer çalışma zamanı polimorfizmi daha. Örneğin, typedefs devralınabilir veya type etiketleme için kullanabilir veya CRTP kullanabilirsiniz. Nihayetinde, bu teknikler (ve diğerleri) kullanılarak uygulansalar bile gerçekten "Derleme zamanı" altına girerler class X : public Y.


Derleme zamanı için ilk profesyonel ile ilgili olarak, bu benim ana sorularım ile ilgilidir. Sadece açık bir arayüzle çalışmak istediğinizi açıkça belirtmek ister misiniz? Yani. 'İstediğim tüm işlevlere sahip olup olmadığınızı umursamıyorum, Z Sınıfından miras almıyorsanız, sizinle bir şey yapmak istemiyorum'. Ayrıca, işaretçi / başvuru kullanırken çalışma zamanı devralma türü bilgilerini kaybetmez, değil mi?
Chris Morris

@ChrisMorris: Hayır. Çalışırsa işe yarıyor, umursamanız gereken tek şey bu. Neden birisine aynı kodu başka bir yere yazsın?
jmoreno

1
@ChrisMorris: Hayır, yapmazdım. Sadece X'e ihtiyacım varsa, kapsüllemenin temel temel ilkelerinden biri, sadece X'i istemem ve önemsemem gerekir. Ayrıca, tür bilgilerini de kaybetmez. Örneğin, yığın, bu tür bir nesne ayıramazsınız. Bir şablonu gerçek türüyle başlatamazsınız. Üzerinde temsili üye işlevlerini çağıramazsınız.
DeadMG

Bir sınıf kullanan Q sınıfına sahip olduğunuz bir durum ne olacak? Q, bir template parametresi alır, böylece örtük arabirimi sağlayan herhangi bir sınıf bunu yapar ya da biz öyle düşünürüz. Görünüşe göre Q sınıfı, kendi iç sınıfının (H olarak adlandırın) Q'nun arayüzünü kullanmasını beklemektedir. Örneğin, H nesnesi yok edildiğinde, Q'ların bazı işlevlerini çağırmalıdır. Bu, örtük bir arayüzde belirtilemez. Böylece, şablonlar başarısız olur. Daha açık bir ifadeyle, birbirinden yalnızca örtülü arabirimlerden daha fazlasını gerektiren sıkı bir şekilde bağlı sınıflar kümesi, şablonların kullanımını yasaklamaktadır.
Chris Morris

Con compiletime: Hata ayıklamak için çirkin, tanımları başlığa koyma zorunluluğu
JFFIGK
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.