Şablon üssü sınıf üyelerine neden bu işaretçi aracılığıyla erişmem gerekiyor?


199

Aşağıdaki sınıflar şablon olmasaydı x, derivedsınıfta sahip olabilirdim . Ancak, aşağıdaki kod ile kullanmak zorundathis->x . Neden?

template <typename T>
class base {

protected:
    int x;
};

template <typename T>
class derived : public base<T> {

public:
    int f() { return this->x; }
};

int main() {
    derived<int> d;
    d.f();
    return 0;
}

1
Ah tanrım. İsim arama ile ilgili bir şey var. Birisi buna yakında cevap vermezse, ararım ve gönderirim (şimdi meşgul).
templatetypedef

@Ed Swangren: Üzgünüm, bu soruyu gönderirken sunulan cevaplar arasında kaçırdım. Cevabı bundan önce uzun zamandır arıyordum.
Ali

6
Bu, iki fazlı ad araması (tüm derleyicilerin varsayılan olarak kullanmadığı) ve bağımlı adlar nedeniyle olur. Bu soruna, xile önek eklemek dışında 3 çözüm vardır this->: 1) önekini kullanın base<T>::x, 2) Bir deyim ekleyin using base<T>::x, 3) İzin veren modu etkinleştiren bir genel derleyici anahtarı kullanın. Bu çözümlerin artıları ve eksileri stackoverflow.com/questions/50321788/…
George Robinson'da

Yanıtlar:


274

Kısa cevap: xBağımlı bir ad oluşturmak için, şablon parametresi bilinene kadar arama ertelenir.

Uzun cevap: bir derleyici bir şablon gördüğünde, template parametresini görmeden hemen belirli kontroller yapması beklenir. Diğerleri parametre bilinene kadar ertelenir. Buna iki fazlı derleme denir ve MSVC bunu yapmaz, ancak standart tarafından gereklidir ve diğer büyük derleyiciler tarafından uygulanır. İsterseniz, derleyici şablonu görür görmez derlemelidir (bir tür iç ayrıştırma ağacı temsiline) ve derlemeyi daha sonraya kadar derlemeyi ertelemelidir.

Belirli örneklemelerinden ziyade şablonun kendisinde gerçekleştirilen denetimler, derleyicinin şablondaki kodun dilbilgisini çözebilmesini gerektirir.

C ++ (ve C) 'de kod dilbilgisini çözmek için bazen bir şeyin tür olup olmadığını bilmeniz gerekir. Örneğin:

#if WANT_POINTER
    typedef int A;
#else
    int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }

A bir türse, bir işaretçi bildirir (global gölgeden başka bir etkisi yoktur x). A bir nesne ise, bu çarpmadır (ve bir operatörün yasadışı aşırı yüklenmesine engel olur ve bir rvalue atar). Yanlışsa, bu hata faz 1'de teşhis edilmelidir , standart tarafından belirli bir örneklemede değil , şablonda bir hata olarak tanımlanır . Şablon hiçbir zaman somutlaştırılmasa bile, A bir ise int, yukarıdaki kod yanlış biçimlendirilmiş ve teşhis edilmesi gerekir, tıpkı foobir şablon olmasa bile , basit bir işlev gibi.

Artık standart , şablon parametrelerine bağlı olmayan isimlerin 1. aşamada çözülebilir olması gerektiğini söylüyor. ABurada bağımlı bir isim değil, türden bağımsız olarak aynı şeyi ifade ediyor T. Bu nedenle, 1. aşamada bulunup kontrol edilebilmesi için şablon tanımlanmadan önce tanımlanması gerekir.

T::AT'ye bağlı bir isim olurdu. 1. aşamada bunun bir tür olup olmadığını bilemeyiz. Sonunda Tbir örneklemede olduğu gibi kullanılacak olan tür henüz tam olarak tanımlanmamıştır ve öyle olsa bile, şablon parametrelerimiz olarak hangi türlerin kullanılacağını bilmiyoruz. Ancak, 1. Aşama, kötü biçimlendirilmiş şablonlar için kontroller yapmak için dilbilgisini çözmeliyiz. Bu yüzden standardın bağımlı isimler için bir kuralı vardır - derleyici , tür typenameolduklarını belirtmekle yetinmedikçe veya belirli belirsiz bağlamlarda kullanılmadıkça , tür olmadığını varsaymalıdır . Örneğin template <typename T> struct Foo : T::A {};, T::Abir temel sınıf olarak kullanılır ve bu nedenle açık bir şekilde bir türdür. Eğer Foobir veri elemanı vardır bazı tip örneğiA iç içe tip A yerine, örneklemede (aşama 2) kodda bir hata var, şablonda (aşama 1) bir hata değil.

Peki ya bağımlı bir temel sınıfa sahip bir sınıf şablonu?

template <typename T>
struct Foo : Bar<T> {
    Foo() { A *x = 0; }
};

A bağımlı bir isim mi, değil mi? Temel sınıflarda, temel sınıfta herhangi bir ad görünebilir. Bu yüzden A'nın bağımlı bir isim olduğunu söyleyebiliriz ve ona tür olmayan olarak davranabiliriz. Bu, Foo'daki her adın bağımlı olduğu istenmeyen bir etkiye sahip olacaktır ve bu nedenle Foo'da kullanılan her türün (yerleşik türler hariç) nitelendirilmesi gerekir. Foo'nun içinde şunları yazmanız gerekir:

typename std::string s = "hello, world";

çünkü std::stringbağımlı bir ad olur ve bu nedenle aksi belirtilmedikçe tür olmayan olarak kabul edilir. Ah!

Tercih ettiğiniz kodun ( return x;) kullanılmasına izin verilmesinde ikinci bir sorun , daha Barönce tanımlanmış olsa Foove xbu tanımda üye olmasa bile , birisinin daha sonra bir tür Bariçin bir uzmanlık tanımlayabilmesidir Baz, bu şekilde Bar<Baz>bir veri üyesi olur xve Foo<Baz>. Bu örneklemede, şablonunuz genel öğeyi döndürmek yerine veri üyesini döndürür x. Ya da tersine, temel şablon tanımına sahip Barolsaydı x, onsuz bir uzmanlık tanımlayabilirler ve şablonunuz xgeri döndürülecek bir küresel arardı Foo<Baz>. Bence bunun, yaşadığınız sorun kadar şaşırtıcı ve üzücü olduğuna karar verildi, ama sessizce şaşırtıcı bir hata atmak yerine, şaşırtıcı.

Bu sorunlardan kaçınmak için yürürlükte olan standart, sınıf şablonlarının bağımlı temel sınıflarının açıkça talep edilmedikçe arama için dikkate alınmadığını söylüyor. Bu, her şeyin sadece bağımlı bir tabanda bulunabilmesi nedeniyle bağımlı olmasını engeller. Ayrıca gördüğünüz istenmeyen bir etkiye sahiptir - temel sınıftan bir şeyleri nitelemeniz gerekir veya bulunmaz. ABağımlı kılmanın üç yaygın yolu vardır :

  • using Bar<T>::A;sınıfta - Aşimdi buna Bar<T>bağlı olan bir şeye atıfta bulunuyor .
  • Bar<T>::A *x = 0;kullanım noktasında - Yine, Akesinlikle içeride Bar<T>. Bu, typenamekullanılmadığından beri çarpmadır , bu yüzden muhtemelen kötü bir örnek, ancak operator*(Bar<T>::A, x)bir değer döndürüp döndürmediğini öğrenmek için örneklemeye kadar beklememiz gerekecek . Kim bilir, belki de ...
  • this->A;kullanım noktasında - Abir üyedir, bu yüzden eğer değilse Foo, temel sınıfta olmalıdır, yine standart bunun bağımlı olduğunu söylüyor.

İki aşamalı derleme zor ve zordur ve kodunuzda ekstra ayrıntılar için bazı şaşırtıcı gereksinimler getirir. Fakat daha ziyade demokrasi gibi, muhtemelen diğerlerinden farklı olarak bir şeyler yapmanın en kötü yolu.

Örneğinizde , temel sınıfta iç içe bir tür return x;olup olmadığını anlamsız bir şekilde iddia edebilirsiniz x, bu nedenle dil (a) bunun bağımlı bir ad olduğunu ve (2) bunu tür olmayan olarak ele almasını ve kodunuz olmadan çalışır this->. Bir dereceye kadar, çözümden sizin durumunuzda geçerli olmayan bir soruna verilen teminat hasarının kurbanı olursunuz, ancak hala temel sınıfınızın potansiyel olarak altında küreselleri gölgeleyen ya da düşündüğünüz isimlere sahip olmayan isimler sunma sorunu var onlar vardı ve onun yerine küresel bir varlık bulundu.

Ayrıca muhtemelen varsayılan bağımlı isimlerin (her nasılsa bir nesne olarak belirtilmediği sürece türünü varsayıyorum) veya varsayılan (daha fazla bağlam duyarlı olması gerektiğini bunun için tersi olması gerektiğini iddia olabilir std::string s = "";, std::stringbaşka bir şey gramer yaptığı için bir tür olarak okunabilir anlam, std::string *s = 0;belirsiz olsa da). Yine, kuralların nasıl kabul edildiğini tam olarak bilmiyorum. Benim tahminim, gerekli olabilecek metin sayfası sayısının, bağlamların bir tür aldığı ve hangi türlerin türünün olmadığı birçok özel kural oluşturmaya karşı hafifletilmesidir.


1
Ooh, güzel detaylı cevap. Aramak için hiç rahatsız etmediğim birkaç şeyi netleştirdik. :) +1
jalf

20
@jalf: C ++ QTWBFAETYNSYEWTKTAAHMITTBGOW gibi bir şey var mı - "Cevabı bilmek istediğinizden ve daha önemli şeyler yapmak istediğinizden emin olmadığınız sürece sık sorulan sorular"?
Steve Jessop

4
olağanüstü cevap, merak ediyorum sorunun SSS uygun olabilir.
Matthieu M.10

Vay, ansiklopedik diyebilir miyiz? highfive Bir ince nokta olsa da: "Foo, iç içe tip A yerine veri üyesi A olan bir türle başlatılırsa, bu, şablonda (aşama 2) bir hata değil, örneği (kod 2) yapan koddaki bir hatadır 1) ". Şablonun hatalı biçimlendirilmediğini söylemek daha iyi olabilir, ancak bu yine de şablon yazarının yanlış varsayımına veya mantıksal bir hataya neden olabilir. İşaretli örnekleme gerçekte amaçlanan kullanıcı tabanı olsaydı, şablon yanlış olur.
Ionoclast Brigham

1
@JohnH. Birkaç derleyicinin uygulandığı -fpermissiveveya benzer olduğu göz önüne alındığında , evet mümkündür. Nasıl uygulandığının ayrıntılarını bilmiyorum, ancak derleyici xgerçek geçici taban sınıfını tanıyana kadar çözmeyi ertelemeli T. Bu nedenle, prensip olarak izin vermeyen modda, ertelediği gerçeğini kaydedebilir, erteleyebilir, bir kez aramayı yapabilir Tve arama başarılı olduğunda önerdiğiniz metni yayınlayabilir. Sadece işe yaradığı durumlarda yapılması çok doğru bir öneri olacaktır: kullanıcının xbaşka bir kapsamdan başka birisini kastetme şansı oldukça küçüktür!
Steve Jessop

13

(10 Ocak 2011'den itibaren orijinal yanıt)

Ben cevap buldum düşünüyorum: GCC sorunu: bir şablon argüman bağlı bir temel sınıfın bir üyesi kullanarak . Cevap gcc'ye özgü değil.


Güncelleme: cevaben mmichael yorumuna gelen taslak N3337 C ++ 11 Standardının:

14.6.2 Bağımlı isimler [temp.dep]
[...]
3 Bir sınıf veya sınıf şablonu tanımında, bir temel sınıf bir şablon parametresine bağlıysa, temel sınıf kapsamı, sınıf şablonunun veya üyesinin tanım noktası veya sınıf şablonunun veya üyesinin örneği sırasında.

İster "standart öyle diyor çünkü" bir cevap olarak sayar, bilmiyorum. Şimdi standardın neden zorunlu olduğunu sorabiliriz, ancak Steve Jessop'un mükemmel cevabı ve diğerleri belirttiği gibi, bu ikinci sorunun cevabı oldukça uzun ve tartışmalı. Ne yazık ki, C ++ Standardı söz konusu olduğunda, standardın neden bir şeyi zorunlu kıldığı konusunda kısa ve bağımsız bir açıklama yapmak neredeyse imkansızdır; bu ikinci soru için de geçerlidir.


11

xMiras sırasında gizlenir. Göstermek için:

template <typename T>
class derived : public base<T> {

public:
    using base<T>::x;             // added "using" statement
    int f() { return x; }
};

25
Bu cevap neden gizlendiğini açıklamıyor .
jamesdlin
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.