Referanslar neden C ++ 'da "const" değil?


87

Bir "const değişkeni" nin atandıktan sonra değişkeni şu şekilde değiştiremeyeceğinizi gösterdiğini biliyoruz:

int const i = 1;
i = 2;

Yukarıdaki program derlenemez; gcc bir hata ile sorar:

assignment of read-only variable 'i'

Sorun değil, anlayabiliyorum, ancak aşağıdaki örnek anlayışımın ötesinde:

#include<iostream>
using namespace std;
int main()
{
    boolalpha(cout);
    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;
    int const &ri = i;
    cout << is_const<decltype(ri)>::value << endl;
    return 0;
}

Çıktılar

true
false

Tuhaf. Bir referans bir isme / değişkene bağlandığında, bu bağlamayı değiştiremeyeceğimizi, bağlı nesnesini değiştireceğimizi biliyoruz. Öyleyse, türünün şununla riaynı olması gerektiğini düşünüyorum i: ne zaman ibir int const, neden rideğil const?


3
Ayrıca, boolalpha(cout)çok sıra dışı. Onun std::cout << boolalphayerine yapabilirsin .
isanae

6
Bunun için çok riayırt edilemez bir "takma" olmak i.
Kaz

1
Bunun nedeni, bir referansın her zaman aynı nesneye bağlı olmasıdır. iaynı zamanda bir referanstır, ancak tarihsel nedenlerden ötürü, bunu açık bir şekilde böyle beyan etmiyorsunuz. Bu nedenle i, bir depolamaya riatıfta bulunan ve aynı depolamaya atıfta bulunan bir referanstır. Ama aradaki Doğada hiçbir fark yoktur i ve ri. Bir referansın bağını değiştiremeyeceğiniz için, onu olarak nitelendirmenize gerek yoktur const. Ve @Kaz yorumunun, doğrulanmış cevaptan çok daha iyi olduğunu iddia edeyim (referansları hiçbir zaman işaretçileri kullanarak açıklamayın, bir ref bir isimdir, bir ptr bir değişkendir).
Jean-Baptiste Yunès

1
Harika soru. Bu örneği görmeden önce , bu durumda da is_constgeri dönmeyi bekliyordum true. Kanımca bu, neden consttemelde geriye doğru olduğuna dair güzel bir örnek ; "değiştirilebilir" bir özellik (a la Rust's mut) daha tutarlı olacak gibi görünüyor.
Kyle Strand

1
Belki de başlık "neden is_const<int const &>::valueyanlış " olarak değiştirilmelidir. veya benzeri; Tip özelliklerinin davranışı hakkında soru sormaktan başka sorunun herhangi bir anlamını görmekte zorlanıyorum
MM

Yanıtlar:


53

Bu mantığa aykırı görünebilir, ancak bunu anlamanın yolu, bazı açılardan, referansların sözdizimsel olarak işaretçiler gibi ele alındığını fark etmektir. .

Bu, bir işaretçi için mantıklı görünüyor :

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

Çıktı:

true
false

Bu mantıklı çünkü onun işaretçi nesnesi olmadığını biliyoruz const (başka bir yere işaret etmek için yapılabilir), işaret edilen nesne.

Dolayısıyla , işaretçinin kendisinin sabitliğinin şu şekilde döndüğünü doğru bir şekilde görüyoruz:false .

Yapmak istiyorsak İşaretçinin kendisiniconst söylemeliyiz:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const* const ri = &i;
    cout << is_const<decltype(ri)>::value << endl;
}

Çıktı:

true
true

Bu yüzden referansla sözdizimsel bir analoji gördüğümüzü düşünüyorum. .

Bununla birlikte, referanslar anlamsal olarak özellikle birinde işaretçilerden farklıdır önemli bir açıdan kez bağlandıktan sonra başka bir nesneye referansı yeniden atamamıza izin verilmez .

Dolayısıyla, referanslar işaretçilerle aynı sözdizimini paylaşsa da kurallar farklıdır ve bu nedenle dil, referansın kendisini bildirmemizi engeller.const böyle:

int main()
{
    boolalpha(cout);

    int const i = 1;
    cout << is_const<decltype(i)>::value << endl;

    int const& const ri = i; // COMPILE TIME ERROR!
    cout << is_const<decltype(ri)>::value << endl;
}

Bunu yapmamıza izin verilmediğini varsayıyorum çünkü dil kuralları referansın aynı şekilde geri tepmesini engellediğinde gerekli görünmüyor. göstericinin yapabileceği şekilde (eğer bildirilmemişse const) .

Yani soruyu cevaplamak için:

S) Neden "başvuru" C ++ 'da bir "sabit" değildir?

Örneğinizde sözdizimi, başvurulan şeyi, constbir işaretçi bildiriyor olsaydınız yapacağı gibi yapar. .

Doğru ya da yanlış olarak bizzat atıf yapmamıza izin verilmiyor constama eğer olsaydık şöyle görünürdü:

int const& const ri = i; // not allowed

S) Bir referansın bir isme / değişkene bağlandığını biliyoruz, bu bağlamayı değiştiremeyiz, bağlı nesnesini değiştiririz. Öyleyse, türünün riaynı olması gerektiğini düşünüyorum i: ne zaman ia int const, neden rideğil const?

Neden başvuranın bağlı olduğu decltype()nesneye aktarılmıyor ?

Sanırım bu, işaretçilerle anlamsal eşdeğerlik içindir ve belki de decltype()(bildirilen tipin) işlevi , bağlanma gerçekleşmeden önce neyin beyan edildiğine bakmaktır .


13
"Referanslar sabit olamaz çünkü her zaman sabittir" diyorsunuz gibi geliyor?
ruakh

2
" Bağlandıktan sonra üzerlerindeki tüm eylemlerin anlamsal olarak atıfta bulundukları nesneye aktarıldığı" doğru olabilir, ancak OP bunun da uygulanmasını bekliyordu ve olmadığını gördü decltype.
ruakh

10
Burada birçok dolambaçlı varsayım ve işaretçiler için şüpheli bir şekilde uygulanabilir analojiler var, bence bu, sonyuanyao'nun yaptığı gibi standardı kontrol ederken gerçek noktaya gelirken konuyu gereğinden fazla karıştırıyor: Bir referans, cv-niteleyici bir tür değildir, bu nedenle olamaz const, bu nedenle std::is_constgeri dönmelidir false. Bunun yerine geri dönmesi gerektiği anlamına gelen ifadeler kullanabilirlerdi true, ama yapmadılar. Bu kadar! İşaretçilerle ilgili tüm bu şeyler, "varsayıyorum", "sanırım" vb. Gerçek bir açıklama sağlamaz.
underscore_d

1
@underscore_d Bu muhtemelen sohbet için buradaki yorumlar bölümünden daha iyi bir sorudur, ancak referanslar hakkında bu şekilde düşündüğünüzde "gereksiz kafa karışıklığı" örneğiniz var mı? Bu şekilde uygulanabilirlerse, onları bu şekilde düşünmenin sorunu nedir? (Bu sorunun decltypebir çalışma zamanı işlemi olmadığı için geçerli olduğunu düşünmüyorum , bu nedenle "çalışma zamanında, referanslar işaretçiler gibi davranıyor" fikri, doğru olsun ya da olmasın, gerçekten geçerli değil.
Kyle Strand

1
Referanslar da sözdizimsel olarak işaretçiler gibi ele alınmaz. Bildirmek ve kullanmak için farklı sözdizimleri vardır. Yani ne demek istediğinden bile emin değilim.
Jordan Melo

55

neden "ri" "const" değil?

std::is_const türün sabit nitelikli olup olmadığını kontrol eder.

T sabit nitelikli bir türse (yani, const veya const geçici), üye sabit değerini true eşittir. Diğer türler için değer yanlıştır.

Ancak referans sabit nitelikli olamaz. Referanslar [dcl.ref] / 1

Cv nitelikli referanslar, cv niteleyicilerinin typedef adı ([dcl.typedef], [temp.param]) veya decltype-specifier ([dcl.type.simple]) kullanılarak tanıtılması dışında biçimsizdir , bu durumda cv niteleyicileri yok sayılır.

Bu nedenle is_const<decltype(ri)>::valuedönecektir falseçünkü ri(başvuru) sabit nitelikli bir tür değildir. Dediğin gibi, başlatmadan sonra bir referansı yeniden bulamayız, bu da referansın her zaman "const" olduğunu gösterir, diğer yandan, const nitelikli referans veya const niteliksiz referans aslında anlamlı olmayabilir.


5
Kabul edilen cevabın tüm waffling varsayımlarından ziyade, doğrudan Standart'a ve dolayısıyla doğrudan konuya.
underscore_d

5
@underscore_d Operasyonun nedenini sorduğunu anlayana kadar bu güzel bir cevaptır. "Çünkü öyle diyor", sorunun bu yönü için pek yararlı değil.
simpleuser

5
@ user9999999 Diğer cevap da bu hususa cevap vermiyor. Bir referans geri tepilemezse, neden is_constgeri dönmüyor true? Bu cevap, işaretçilerin isteğe bağlı olarak nasıl yeniden yerleştirilebilir olduğuna dair bir benzetme yapmaya çalışır, oysa referanslar değildir - ve bunu yaparken aynı nedenle kendi kendine çelişkiye yol açar. Standardı yazanların biraz keyfi bir karar dışında gerçek bir açıklaması olduğundan emin değilim ve bazen umabileceğimizin en iyisi budur. Dolayısıyla bu cevap.
underscore_d

2
Bir başka önemli yönü, bence, yani decltypeolan bir işlev değildir ve bu nedenle doğrudan referans kendisi çalışır nesneye atıfta-ziyade üzerinde. (Bu belki de "referanslar temelde işaretçilerdir" cevabıyla daha alakalı olabilir, ancak yine de bu örneği kafa karıştırıcı yapan ve bu nedenle burada bahsetmeye değer olan şeyin bir parçası olduğunu düşünüyorum.)
Kyle Strand

3
@KyleStrand'dan gelen yorumu daha da açıklamak için: decltype(name)bir generalden farklı davranır decltype(expr). Yani, örneğin, decltype(i)beyan türüdür iolan const int, süre decltype((i))olacaktır int const &.
Daniel Schepler


8

Makrolar neden değil const? Fonksiyonlar? Değişmezler? Türlerin isimleri?

const şeyler, değişmez şeylerin yalnızca bir alt kümesidir.

Başvuru türleri sadece - türler - constolduğundan, diğer türlerle (özellikle işaretçi türlerinde) simetri için hepsinde-niteleyicinin kullanılması mantıklı olabilir , ancak bu çok çabuk sıkıcı olur.

C ++ gerektiren varsayılan olarak iletmenin nesneleri olsaydı mutablebir şey üzerinde anahtar kelime etmedi olmak istiyorum const, o zaman bu kolay olurdu: sadece programcılar eklemek için izin vermezmutable referans türlerine.

Olduğu gibi, nitelik olmadan değişmezler.

Onlar değildir yana, constulaşım kolaylığı, muhtemelen olacağını daha için kafa karıştırıcıis_const gerçek verim için bir başvuru türüne.

Bunu makul bir uzlaşma olarak görüyorum, özellikle de değişmezlik zaten bir referansı mutasyona uğratacak hiçbir sözdizimi olmadığı gerçeği tarafından zorlandığı için.


6

Bu, C ++ 'daki bir tuhaflık / özelliktir. Referansları tür olarak düşünmesek de, aslında bunlar yazım sisteminde "oturur". Bu garip görünse de (referanslar kullanıldığında, referans anlambiliminin otomatik olarak oluştuğu ve referansın "yoldan çıktığı" göz önüne alındığında), referansların dışında ayrı bir öznitelik olarak değil de tip sisteminde modellenmesinin savunulabilir nedenleri vardır. yazın.

İlk olarak, beyan edilen bir ismin her özniteliğinin tür sisteminde olması gerekmediğini düşünelim. C dilinden, "depolama sınıfı" ve "bağlantı" var. Gibi bir isim sokulabilir extern const int riburada, externstatik depolama sınıfı ve bağlantı varlığı belirtir. Tip sadece const int.

C ++, açık bir şekilde, ifadelerin tip sisteminin dışında niteliklere sahip olduğu fikrini benimser. Dil artık, bir ifadenin sergileyebileceği artan sayıdaki tip dışı öznitelikleri organize etme girişimi olan bir "değer sınıfı" kavramına sahiptir.

Yine de referanslar türlerdir. Neden?

Eskiden C ++ öğreticilerinde, türe sahip olarak const int &risunulduğu rigibi bir bildirimin sunulduğu const int, ancak semantiği referans aldığı açıklanırdı . Bu referans semantiği bir tür değildi; bu sadece ad ve depolama konumu arasında olağandışı bir ilişkiyi gösteren bir tür nitelikti. Dahası, referansların türler olmadığı gerçeği, tür oluşturma sözdizimi izin verse bile, neden referanslara dayalı türler oluşturamadığınızı rasyonelleştirmek için kullanıldı. Örneğin, mümkün olmayan referanslara diziler veya işaretçiler: const int &ari[5]ve const int &*pri.

Fakat gerçekte referansları olan türleri ve böylece decltype(ri)olumlu olan bir referans türü düğümü alır. Temel alınan türe ulaşmak için tür ağacında bu düğümü geçmelisiniz remove_reference.

Eğer kullandığınız zaman ri, referans şeffaf böylece, çözüldükten ri"gibi görünüyor ve hissediyor i" ve bunun için bir "takma" denebilir. Tip sisteminde, riaslında " referans olan" bir türe sahiptir . const int .

Referans türleri neden?

Referanslar tür değilse , bu işlevlerin aynı türe sahip olduğu kabul edilir:

void foo(int);
void foo(int &);

Bu, hemen hemen apaçık ortada olan nedenlerden dolayı olamaz. Aynı türe sahiplerse, bu, her iki tanımın da her iki tanım için uygun olacağı anlamına gelir.(int) işlevin bir referans aldığından şüphelenilmesi gerekir.

Benzer şekilde, başvurular tür değilse, bu iki sınıf bildirimi eşdeğer olacaktır:

class foo {
  int m;
};

class foo {
  int &m;
};

Bir çeviri biriminin bir bildirimi kullanması ve aynı programdaki başka bir çeviri biriminin diğer bildirimi kullanması doğru olacaktır.

Gerçek şu ki, bir referans, uygulamada bir fark anlamına gelir ve bunu türden ayırmak imkansızdır, çünkü C ++ 'da tip, bir varlığın uygulanmasıyla ilgilidir: bitlerdeki "düzeni" tabiri caizse. İki işlev aynı türe sahipse, aynı ikili çağrı kurallarıyla çağrılabilirler: ABI aynıdır. İki yapı veya sınıf aynı türe sahipse, bunların düzeni ve tüm üyelere erişimin anlambilimiyle aynıdır. Referansların varlığı türlerin bu yönlerini değiştirir ve bu nedenle bunları tür sistemine dahil etmek basit bir tasarım kararıdır. (Bununla birlikte, burada bir karşı argümana dikkat edin: yapı / sınıf üyesi olabilirstatic , bu da gösterimi değiştirir; ancak bu tür değildir!)

Bu nedenle, referanslar "ikinci sınıf vatandaşlar" olarak tür sistemindedir (ISO C'deki işlevler ve dizilerden farklı değildir). Referanslara işaretçi bildirmek veya bunların dizileri gibi referanslarla "yapamayacağımız" bazı şeyler vardır. Ama bu tip olmadıkları anlamına gelmez. Mantıklı bir şekilde tip değiller.

Tüm bu ikinci sınıf kısıtlamalar gerekli değildir. Referans yapıları olduğu göz önüne alındığında, referans dizileri olabilir! Örneğin

// fantasy syntax
int x = 0, y = 0;
int &ar[2] = { x, y };

// ar[0] is now an alias for x: could be useful!

Bu sadece C ++ 'da uygulanmadı, hepsi bu. Referanslara işaretçiler hiçbir anlam ifade etmez, çünkü bir referanstan kaldırılan bir işaretçi sadece referans alınan nesneye gider. Referans dizisi olmamasının olası nedeni, C ++ insanlarının dizileri C'den miras alınan, onarılamaz birçok şekilde bozulmuş bir tür düşük seviyeli özellik olarak görmeleri ve dizilere dokunmak istememeleridir. yeni her şeyin temeli. Referans dizilerinin varlığı, referansların nasıl türler olması gerektiğine dair net bir örnek oluşturacaktır.

Olmayan-const -qualifiable türleri: çok ISO C90 bulundu!

Bazı yanıtlar, referansların bir constniteleyici almadığına işaret ediyor . Deklarasyon Çünkü, daha doğrusu kırmızı ringa const int &ri = ibile değildir deneyen bir hale getirmek için constulaşım kolaylığı başvuru: (kendisi olmayan bir const nitelikli tip bir referans var const). Tıpkı const in *ribir şeye işaretçi bildirdiği gibi const, ancak bu işaretçi kendisi değildir const.

Bununla birlikte, referansların constniteleyiciyi kendilerinin taşıyamayacağı doğrudur .

Yine de bu o kadar tuhaf değil. ISO C 90 dilinde bile her tür olamaz const. Yani diziler olamaz.

İlk olarak, bir const dizisini bildirmek için sözdizimi mevcut değildir: int a const [42]hatalı.

Bununla birlikte, yukarıdaki beyannamenin yapmaya çalıştığı şey bir ara maddeyle ifade edilebilir typedef:

typedef int array_t[42];
const array_t a;

Ama bu göründüğü gibi yapmıyor. Bu bildiride, değil mi aaldığı constnitelikli ama unsurlar! Yani, a[0]bir const int, ama asadece "int dizisi" dir. Sonuç olarak, bu bir teşhis gerektirmez:

int *p = a; /* surprise! */

Bu yapar:

a[0] = 1;

Yine, bu, referansların diziler gibi tür sisteminde bir anlamda "ikinci sınıf" olduğu fikrinin altını çizer.

Diziler de referanslar gibi "görünmez bir dönüştürme davranışına" sahip olduklarından, analojinin daha da derin bir şekilde geçerli olduğuna dikkat edin. Programcı herhangi bir açık operatör kullanmak zorunda kalmadan, tanımlayıcı , sanki ifade kullanılmış gibi aotomatik olarak bir int *işaretçiye dönüşür &a[0]. Bu, bir referansın ri, onu birincil ifade olarak kullandığımızda i, bağlı olduğu nesneyi sihirli bir şekilde ifade etme şekline benzer . Bu, "işaretçi bozunması dizisi" gibi başka bir "bozulma" dır.

Ve "işaretçi dizi" nin yanlış bir şekilde "dizilerin C ve C ++ 'da işaretçiler olduğunu" düşünerek çürümesi ile karıştırılmamamız gerektiği gibi, referansların da kendilerine ait hiçbir türü olmayan takma adlar olduğunu düşünmemeliyiz.

Zaman decltype(ri)onun referan nesnesine referans olağan dönüşüm bastırır, bu çok farklı değildir sizeof adizi-to-pointer dönüşüm bastırılması ve işletim dizi türü kendisi boyutunu hesaplamak.


Burada pek çok ilginç ve yararlı bilginiz var (+1), ancak bu az çok "referansların yazım sisteminde olduğu" gerçeğiyle sınırlıdır - bu, OP'nin sorusunu tamamen yanıtlamaz. Bu cevap ve @ songyuanyao'nun cevabı arasında, durumu anlamak için fazlasıyla bilgi olduğunu söyleyebilirim, ancak tam bir cevaptan ziyade bir cevap için gerekli arka plan gibi geliyor .
Kyle Strand

1
Ayrıca, bu cümlenin vurgulanmaya değer olduğunu düşünüyorum: "ri kullandığınızda, referans şeffaf bir şekilde çözülür ..." Bir kilit nokta (yorumlarda bahsettiğim, ancak şu ana kadar yanıtların hiçbirinde gösterilmemiştir. ) yani decltype gelmez o kadar (bu şeffaf çözünürlük gerçekleştirmek bir işlev değil ri) açıkladığınız anlamda "kullanılan" değil. Bu , tip sisteme tüm odaklanmanıza çok iyi uyuyor - anahtar bağlantı decltype, bir tip-sistem operasyonu .
Kyle Strand

Hmm, dizilerle ilgili şeyler bir teğet gibi geliyor, ancak son cümle yardımcı oluyor.
Kyle Strand

..... bu noktada zaten upvoted ettik ve gerçekten .... kazanıyor ya da benim eleştirilerine dinleme yoluyla şey kaybetmeden değiliz bu yüzden, OP değilim gerçi
Kyle Strand

Açıkçası kolay (ve doğru cevap) sadece bizi standarda işaret ediyor, ancak bazıları bazı kısımlarının varsayım olduğunu iddia etse de buradaki arka plan düşüncesini seviyorum. +1.
Steve Kidd

-6

const X & x ”, x'in bir X nesnesinin takma adını verdiği anlamına gelir, ancak bu X nesnesini x aracılığıyla değiştiremezsiniz.

Ve std :: is_const'a bakın .


bu soruya cevap vermeye bile kalkışmıyor.
bolov
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.