"İs_base_of" nasıl çalışır?


118

Aşağıdaki kod nasıl çalışır?

typedef char (&yes)[1];
typedef char (&no)[2];

template <typename B, typename D>
struct Host
{
  operator B*() const;
  operator D*();
};

template <typename B, typename D>
struct is_base_of
{
  template <typename T> 
  static yes check(D*, T);
  static no check(B*, int);

  static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};

//Test sample
class Base {};
class Derived : private Base {};

//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
  1. Bunun Bözel üs olduğunu unutmayın . Bu nasıl çalışıyor?

  2. operator B*()Const olduğunu unutmayın . Neden önemlidir?

  3. Neden daha template<typename T> static yes check(D*, T);iyi static yes check(B*, int);?

Not : İndirgenmiş sürümüdür (makrolar kaldırılmıştır) boost::is_base_of. Ve bu, çok çeşitli derleyiciler üzerinde çalışır.


4
Bir şablon parametresi ve gerçek bir sınıf adı için aynı tanımlayıcıyı kullanmanız çok kafa karıştırıcı ...
Matthieu M.

1
@Matthieu M., Düzeltmeyi kendim üstlendim :)
Kirill V. Lyadvinsky

2
Bir süre önce alternatif bir uygulama yazdım is_base_of: ideone.com/T0C1V Yine de eski GCC sürümleriyle çalışmıyor (GCC4.3 iyi çalışıyor).
Johannes Schaub - litb

3
Tamam, yürüyüşe çıkacağım.
jokoon

2
Bu uygulama doğru değil. is_base_of<Base,Base>::valueolmalı true; bu geri döner false.
chengiz

Yanıtlar:


109

Akraba iseler

Bir an için bunun Baslında bir temel olduğunu varsayalım D. Daha sonra çağrı için check, her iki sürüm de uygulanabilir çünkü veHost biçimine dönüştürülebilir . Bu, sırasıyla ve arasında ile açıklandığı gibi kullanıcı tanımlı bir dönüşüm dizisidir . Sınıfı dönüştürebilen dönüştürme fonksiyonlarını bulmak için, aşağıdaki aday fonksiyonlar ilk fonksiyona göre sentezlenir .D* B*13.3.3.1.2Host<B, D>D*B*check13.3.1.5/1

D* (Host<B, D>&)

İlk dönüştürme işlevi bir aday B*değildir çünkü dönüştürülemez D*.

İkinci işlev için aşağıdaki adaylar mevcuttur:

B* (Host<B, D> const&)
D* (Host<B, D>&)

Bunlar, ana bilgisayar nesnesini alan iki dönüştürme işlevi adayıdır. Birincisi onu const referansıyla alır, ikincisi almaz. Böylece ikincisi, const olmayan *thisnesne ( örtük nesne argümanı ) için daha iyi bir eşleşmedir ve ikinci işleve 13.3.3.2/3b1sb4dönüştürmek için kullanılır .B*check

Eğer istiyorsanız kaldırmak const, aşağıdaki adayları olurdu

B* (Host<B, D>&)
D* (Host<B, D>&)

Bu, artık sabitlikle seçim yapamayacağımız anlamına gelir. Sıradan bir aşırı yük çözümleme senaryosunda, normal olarak dönüş türü aşırı yük çözümlemesine katılmayacağı için çağrı artık belirsiz olacaktır. Ancak dönüştürme işlevleri için bir arka kapı vardır. İki dönüşüm işlevi eşit derecede iyi ise, o zaman bunların dönüş türü kimin en iyi olduğuna karar verir 13.3.3/1. Eğer const kaldırmak olsaydı Böylece, daha sonra ilk, çünkü alınacağını B*dönüştürür daha iyi B*daha D*için B*.

Şimdi hangi kullanıcı tanımlı dönüşüm sırası daha iyidir? İkinci kontrol işlevi için mi yoksa ilk kontrol işlevi için mi? Kural, kullanıcı tanımlı dönüşüm dizilerinin ancak aynı dönüştürme işlevini veya yapıcıyı kullanmaları durumunda karşılaştırılabilmesidir 13.3.3.2/3b2. Buradaki durum tam olarak budur: Her ikisi de ikinci dönüştürme işlevini kullanır. Bu nedenle const'ın önemli olduğuna dikkat edin çünkü derleyiciyi ikinci dönüştürme işlevini almaya zorlar.

Onları karşılaştırabildiğimize göre - hangisi daha iyi? Kural, dönüştürme işlevinin dönüş türünden hedef türüne daha iyi dönüşümün kazanmasıdır (yine ile 13.3.3.2/3b2). Bu durumda, D*yerine D*daha iyi dönüşür B*. Böylece ilk işlev seçilir ve kalıtımı tanırız!

Biz ihtiyacım olmadı çünkü o Bildirimi aslında bir temel sınıf dönüştürmek, biz böylece tanıyabilir özel miras biz dönüştürebilirsiniz edip, çünkü D*bir hiç B*uygun miras şeklinde bağlı değildir4.10/3

İlişkili değillerse

Şimdi bunların kalıtımla ilişkili olmadığını varsayalım. Böylece ilk işlev için aşağıdaki adaylara sahibiz

D* (Host<B, D>&) 

Ve ikinci için şimdi başka bir setimiz var

B* (Host<B, D> const&)

Dönüştürdüğümüz olamaz yana D*hiç B*bir miras ilişkisi yoksa eğer, şimdi iki kullanıcı tanımlı dönüşüm dizileri arasında hiçbir ortak dönüşüm fonksiyonu var! Bu nedenle, ilk işlevin bir şablon olduğu gerçeği olmasaydı belirsiz olurduk . Şablonlar, göre eşit derecede iyi olan şablon olmayan bir işlev olduğunda ikinci tercihtir 13.3.3/1. Böylece, şablon olmayan işlevi (ikincisi) seçeriz Bve ve arasında kalıtım olmadığını anlarız D.


2
Ah! Andreas paragrafı doğru yapmıştı, çok kötü cevap vermemişti :) Vakit ayırdığınız için teşekkürler, keşke onu favorilerime koyabilseydim.
Matthieu M.

2
Bu şimdiye kadarki en sevdiğim cevap olacak ... bir soru: C ++ standardının tamamını okudunuz mu yoksa sadece C ++ komitesinde mi çalışıyorsunuz? Tebrikler!
Marco A.

4
C ++ komitesinde çalışan @DavidKernin, C ++ 'nın nasıl çalıştığını otomatik olarak anlamanızı sağlamaz :) Bu yüzden, benim yaptığım ayrıntıları bilmek için gerekli olan Standardın bölümünü kesinlikle okumalısınız. Hepsini okumadım, bu yüzden Standart kitaplığın çoğunda veya iş parçacığı ile ilgili soruların çoğunda kesinlikle yardımcı olamıyorum :)
Johannes Schaub - litb

1
@underscore_d Adil olmak gerekirse, teknik özellik std :: özelliklerinin bazı derleyici sihirlerini kullanmasını yasaklamaz, böylece standart kitaplık uygulayıcıları istedikleri gibi kullanabilir . Derleme süresini ve bellek kullanımını hızlandırmaya da yardımcı olan şablon akrobatiklerinden kaçınırlar. Arayüz benzese bile bu doğrudur std::is_base_of<...>. Hepsi kaputun altında.
Johannes Schaub -

2
Tabii ki, genel kütüphaneler boost::kullanmadan önce bu içsel bilgilere sahip olduklarından emin olmak ister. Bir şeyleri derleyicinin yardımı olmadan uygulamak için aralarında bir tür "meydan okuma kabul edilmiş" zihniyet olduğunu hissediyorum :)
Johannes Schaub - litb

24

Adımlara bakarak nasıl çalıştığını bulalım.

Bölüm ile başlayın sizeof(check(Host<B,D>(), int())). Derleyici, check(...)bunun bir işlev çağrısı ifadesi olduğunu çabucak görebilir, bu nedenle üzerinde aşırı yük çözümlemesi yapması gerekir check. Mevcut iki aday aşırı yüklemesi vardır template <typename T> yes check(D*, T);ve no check(B*, int);. Birincisi seçilirse, alırsın sizeof(yes), yoksasizeof(no)

Sonra, aşırı yük çözünürlüğüne bakalım. İlk aşırı yükleme, bir şablon somutlaştırmadır check<int> (D*, T=int)ve ikinci adaydır check(B*, int). Sağlanan gerçek argümanlar Host<B,D>ve int(). İkinci parametre onları açıkça ayırt etmez; yalnızca ilk aşırı yüklemeyi bir şablon haline getirmeye hizmet etti. Şablon bölümünün neden alakalı olduğunu daha sonra göreceğiz.

Şimdi gerekli olan dönüştürme dizilerine bakın. İlk aşırı yükleme için, Host<B,D>::operator D*kullanıcı tanımlı bir dönüşümümüz var. İkincisi, aşırı yük daha zordur. B * 'ye ihtiyacımız var, ancak muhtemelen iki dönüşüm dizisi var. Bir yolu Host<B,D>::operator B*() const. Eğer (ve ancak) B ve D kalıtımla ilişkiliyse Host<B,D>::operator D*()+ dönüşüm dizisi D*->B*var olacaktır. Şimdi D'nin aslında B'den miras aldığını varsayalım. İki dönüşüm dizisi Host<B,D> -> Host<B,D> const -> operator B* const -> B*ve Host<B,D> -> operator D* -> D* -> B*.

Dolayısıyla, ilgili B ve D için no check(<Host<B,D>(), int())belirsiz olur. Sonuç olarak, şablonlu yes check<int>(D*, int)seçilir. Bununla birlikte, D, B'den miras almıyorsa, o zaman no check(<Host<B,D>(), int())belirsiz değildir. Bu noktada, en kısa dönüştürme sırasına göre aşırı yük çözümü gerçekleşemez. Bununla birlikte, eşit dönüştürme dizileri verildiğinde, aşırı yük çözünürlüğü şablon olmayan işlevleri tercih eder, yani no check(B*, int).

Artık kalıtımın özel olmasının neden önemli olmadığını anlıyorsunuz: bu ilişki yalnızca no check(Host<B,D>(), int())erişim kontrolü gerçekleşmeden önce aşırı yük çözümlemesini ortadan kaldırmaya hizmet ediyor . Ayrıca neden operator B* constsabit olması gerektiğini de görüyorsunuz : aksi takdirde Host<B,D> -> Host<B,D> constadıma gerek yoktur, belirsizlik yoktur ve no check(B*, int)her zaman seçilecektir.


Açıklamanız varlığını açıklamıyor const. Cevabınız doğruysa const, hayır gerekmez. Ama bu doğru değil. Kaldır constve hile işe yaramayacak.
Alexey Malistov

Const olmadan iki dönüşüm dizisi no check(B*, int)artık belirsiz değildir.
MSalters

Sadece ayrılırsanız no check(B*, int), o zaman ilgili Bve Dbelirsiz olmaz. Derleyici operator D*(), dönüşümü açık bir şekilde gerçekleştirmeyi seçer çünkü bir sabiti yoktur. Biraz ters yönde: Eğer const'i kaldırırsanız , bir belirsizlik duygusu ortaya koyarsınız, ancak bu, benzer operator B*()bir işaretçi dönüşümü gerektirmeyen üstün bir dönüş türü sağlaması gerçeğiyle çözülür . B*D*
Johannes Schaub -

Bu gerçekten nokta: belirsizlik bir almak için iki farklı dönüşüm dizileri arasındadır B*gelen <Host<B,D>()geçici.
MSalters

Bu daha iyi bir cevap. Teşekkürler! Öyleyse, anladığım gibi, bir işlev daha iyi, ancak belirsiz ise, o zaman başka bir işlev seçilir?
user1289

4

privateBit tamamen tarafından göz ardı edilir is_base_ofaşırı yük çözünürlük erişilebilirlik kontrol etmeden önce oluşur çünkü.

Bunu basitçe doğrulayabilirsiniz:

class Foo
{
public:
  void bar(int);
private:
  void bar(double);
};

int main(int argc, char* argv[])
{
  Foo foo;
  double d = 0.3;
  foo.bar(d);       // Compiler error, cannot access private member function
}

Aynısı burada da geçerlidir, Bözel bir üs olması, çekin yapılmasını engellemez, yalnızca dönüşümü engeller, ancak asla gerçek dönüşümü istemeyiz;)


Tür. Hiçbir temel dönüştürme gerçekleştirilmez. hostisteğe bağlı olarak dönüştürülür D*veya B*unevaluated ifadede. Bazı nedenlerden dolayı, belirli koşullar altında D*tercih edilir B*.
Potatoswatter

Cevabın 13.3.1.1.2'de olduğunu düşünüyorum ama detayları henüz çözmedim :)
Andreas Brinck,

Cevabım sadece "neden özel işler bile" kısmını açıklıyor, sellibitze'nin cevabı kesinlikle daha eksiksiz, ancak vakalara bağlı olarak tam çözüm sürecinin net bir açıklamasını merakla bekliyorum.
Matthieu M.

2

Muhtemelen aşırı yük çözümü için kısmi siparişle ilgisi vardır. D'nin B'den türetilmesi durumunda D *, B * 'den daha uzmanlaşmıştır.

Kesin ayrıntılar oldukça karmaşıktır. Çeşitli aşırı yük çözme kurallarının önceliklerini bulmanız gerekir. Kısmi sipariş birdir. Uzunluklar / tür dönüşüm dizileri bir diğeridir. Son olarak, iki uygulanabilir fonksiyonun eşit derecede iyi olduğu kabul edilirse, şablon olmayanlar, fonksiyon şablonları yerine seçilir.

Bu kuralların nasıl etkileşim kurduğuna bakmaya hiç ihtiyaç duymadım. Ancak görünen o ki, diğer aşırı yük çözme kurallarına kısmi sıralama hakim. D, B'den türetilmediğinde, kısmi sıralama kuralları uygulanmaz ve şablon olmayan daha çekici olur. D, B'den türediğinde, kısmi sıralama devreye girer ve göründüğü gibi işlev şablonunu daha çekici hale getirir.

Mirasın ayrıcalıklı olmasına gelince: kod hiçbir zaman D * 'den B *' ye genel miras gerektiren bir dönüşüm istemez.


Sanırım bunun gibi bir şey, destek arşivleri hakkında kapsamlı bir tartışmanın uygulanması is_base_ofve katkıda bulunanların bunu sağlamak için geçtikleri döngüleri gördüğümü hatırlıyorum .
Matthieu M.

The exact details are rather complicated- önemli olan bu. Lütfen açıkla. Bilmek istiyorum
Alexey Malistov

@Alexey: Seni doğru yöne yönlendirdiğimi düşündüm. Bu durumda çeşitli aşırı yük çözme kurallarının nasıl etkileşime girdiğine bakın. Bu aşırı yükleme durumunun çözümü açısından B'den türeyen D ile B'den kaynaklanmayan D arasındaki tek fark kısmi sıralama kuralıdır. Aşırı yük çözümlemesi, C ++ standardının 13. maddesinde açıklanmaktadır. Ücretsiz olarak bir taslak edinebilirsiniz: open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1804.pdf
sellibitze

Aşırı yük çözünürlüğü, bu taslakta 16 sayfaya yayılır. Sanırım, bu dava için kuralları ve aralarındaki etkileşimi gerçekten anlamanız gerekiyorsa, paragraf 13.3'ün tamamını okumalısınız. Burada% 100 doğru ve standartlarınıza uygun bir cevap alacağıma güvenmem.
sellibitze

Lütfen ilgileniyorsanız bunun açıklaması için cevabıma bakın.
Johannes Schaub -

0

İkinci sorunuzun ardından, const için olmasaydı, B == D ile somutlaştırılırsa Host'un kötü biçimlendirileceğini unutmayın.Ancak is_base_of, her sınıfın kendi tabanı olacak şekilde tasarlandığından, dönüşüm operatörlerinden birinin sabit olmak

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.