Referans dizileri neden yasadışı?


152

Aşağıdaki kod derlenmez.

int a = 1, b = 2, c = 3;
int& arr[] = {a,b,c,8};

C ++ standardı bu konuda ne diyor?

Bir referans içeren bir sınıf tanımlayabileceğimi ve daha sonra aşağıda gösterildiği gibi bu sınıfın bir dizisini oluşturabileceğimi biliyorum. Ama yukarıdaki kodun neden derlenmediğini gerçekten bilmek istiyorum.

struct cintref
{
    cintref(const int & ref) : ref(ref) {}
    operator const int &() { return ref; }
private:
    const int & ref;
    void operator=(const cintref &);
};

int main() 
{
  int a=1,b=2,c=3;
  //typedef const int &  cintref;
  cintref arr[] = {a,b,c,8};
}

Bir dizi referansı simüle etmek struct cintrefyerine kullanmak mümkündür const int &.


1
Dizi geçerli olsa bile, içinde ham bir '8' değeri depolamak işe yaramaz. "İntlink value = 8;" yaparsanız, korkunç bir şekilde ölür, çünkü hemen hemen "const int & value = 8;" e çevrilir. Bir referans bir değişkene başvurmalıdır.
Grant Peters

3
intlink value = 8;çalışır. inanmıyor musun kontrol et.
Alexey Malistov

7
Alexey'in belirttiği gibi, bir rvalue'yu bir const referansına bağlamak tamamen geçerlidir.
avakar

1
Ne yok işi olmasıdır operator=. Referanslar yeniden yerleştirilemez. Eğer gerçekten bu tür semantik istiyorsanız - ben şahsen onlar pratik açıdan yararlı olduğunu bir durum tespit ettik olsa - daha sonra std::reference_wrapperaslında bir işaretçi saklar ama referans benzeri sağladığından, bunu yapmak için bir yol olacağını operators ve yok yerleştirdikten izin verir. Ama sonra sadece bir işaretçi kullanırım!
underscore_d

1
= Operatörü özeldir ve gerçeklenmemiştir, diğer bir deyişle C ++ 11'de = sildir.
Jimmy Hartzell

Yanıtlar:


153

Standart hakkındaki sorunuza cevap verirken C ++ Standardı §8.3.2 / 4'ten alıntı yapabilirim :

Referanslara referanslar, referans dizileri ve referanslara işaretçiler olmayacaktır.


37
Söyleyecek daha ne var?
polyglot

9
işaretçiler dizilerine sahip olmamızla aynı nedenle, aynı bilgi, ancak farklı kullanım, hayır?
neu-rah

6
Derleyici geliştiricileri, bir uzantı olarak referans dizilerine izin vermeye karar verselerdi, bu başarılı olur muydu? Yoksa bu uzantıyı tanımlamayı çok kafa karıştırıcı hale getirecek bazı gerçek sorunlar - belki de belirsiz kodlar - var mı?
Aaron McDaid

25
@AaronMcDaid Bence bu ve benzeri tartışmalarda çok bariz bir şekilde eksik olan gerçek neden, eğer birinin bir dizi referansı olsaydı, bir öğenin adresi ile gönderdiği adres arasında bir belirsizlik nasıl giderilebilirdi? Anında itiraz böyle olduğunu 'You olursa olsun bir dizi / konteyner / bir başvuru koyamazsınız' olabilir bir koyun structolan tek üyesi bir referanstır. Ama sonra, bunu yaparak, şimdi hem bir referansa hem de adlandırılacak bir ana nesneye sahip olursunuz ... Bu, şimdi istediğiniz adresi açık bir şekilde belirtebileceğiniz anlamına gelir. Açık görünüyor ... öyleyse neden kimse söylemedi?
underscore_d

101
@polyglot NedenWhat more is there to say? için bir gerekçe ? Ben tamamen Standart'la ilgiliyim, ancak sadece alıntı yapmak ve tartışmanın sonunun bu olduğunu varsaymak, kullanıcıların eleştirel düşüncelerini yok etmenin kesin bir yolu gibi görünüyor - dilin gelişmesi için herhangi bir potansiyel, örneğin ihmal sınırlarının ötesinde veya yapay kısıtlama. Çok fazla 'çünkü Standart' burada - ve yeterli değil 'diyor ve aşağıdaki pratik nedenlerden dolayı bunu söylemek çok mantıklı. Olumlu oyların, ikincisini keşfetmeye bile çalışmadan yalnızca birinciyi söyleyen cevaplara neden bu kadar hevesle aktığını bilmiyorum.
underscore_d

65

Referanslar nesne değildir. Kendi depolarına sahip değiller, sadece mevcut nesnelere başvuruyorlar. Bu nedenle referans dizilerine sahip olmak mantıklı değil.

Başka bir nesneye başvuran hafif bir nesne istiyorsanız, bir işaretçi kullanabilirsiniz. structTüm structörnekler için tüm başvuru üyeleri için açık başlatma sağlarsanız , yalnızca bir başvuru üyesiyle birlikte dizilerde nesneler olarak kullanabilirsiniz . Referanslar varsayılan olarak büyük harfle yazılamaz.

Düzenleme: jia3ep'in belirttiği gibi, bildirimlerle ilgili standart bölümde referans dizileri üzerinde açık bir yasak vardır.


6
Referanslar, doğaları gereği sabit işaretçilerle aynıdır ve bu nedenle, bir şeyi işaret etmek için biraz hafıza (depolama) kullanırlar.
inazaruk

7
Mutlaka değil. Derleyici, örneğin yerel nesneye bir başvuruysa, adresi depolamaktan kaçınabilir.
EFraim

4
Şart değil. Yapılardaki referanslar genellikle biraz depolanır. Yerel referanslar genellikle bunu yapmaz. Her iki durumda da, katı standart anlamda, referanslar nesne değildir ve (bu benim için yeniydi) adlandırılmış referanslar aslında değişken değildir .
CB Bailey

26
Evet, ancak bu bir uygulama detayıdır. C ++ modelindeki bir nesne , tiplenmiş bir depolama bölgesidir. Bir referans, açıkça bir nesne değildir ve herhangi bir bağlamda depolamayı alacağının garantisi yoktur.
CB Bailey

9
Bunu şu şekilde ifade edin: 16 foo referansına 16 referans dışında bir yapıya sahip olabilirsiniz ve bunu tam olarak benim referans dizimi kullanmak istediğim gibi kullanabilirsiniz - 16 foo referansına indeksleyememeniz dışında . Bu bir dil siğilidir IMHO.
greggo

31

Bu enteresan bir tartışma. Açıkçası başvuru dizileri tamamen yasadışıdır, ancak IMHO'nun nedeni 'nesneler değiller' veya 'boyutları yok' demek kadar basit değildir. Dizilerin kendilerinin C / C ++ 'da tam teşekküllü nesneler olmadığını belirtmek isterim - buna itiraz ederseniz, bir dizi' sınıf 'şablon parametresi olarak kullanarak bazı stl şablon sınıflarını başlatmayı deneyin ve ne olduğunu görün. Onları iade edemez, atayamaz, parametre olarak geçiremezsiniz. (bir dizi parametresi bir işaretçi olarak kabul edilir). Ancak dizi dizisi yapmak yasaldır. Referansların derleyicinin hesaplayabileceği ve hesaplaması gereken bir boyutu vardır - bir referansı sizeof () edemezsiniz, ancak referanslardan başka hiçbir şey içermeyen bir yapı oluşturabilirsiniz. Referansları uygulayan tüm işaretçileri içerecek büyüklükte olacaktır. Yapabilirsin'

struct mys {
 int & a;
 int & b;
 int & c;
};
...
int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs.a += 3  ;  // add 3 to ivar1

Aslında bu satırı yapı tanımına ekleyebilirsiniz

struct mys {
 ...
 int & operator[]( int i ) { return i==0?a : i==1? b : c; }
};

... ve şimdi bir dizi başvuruya çok benzeyen bir şeye sahibim:

int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs[1] = my_refs[2]  ;  // copy arr[12] to ivar2
&my_refs[0];               // gives &my_refs.a == &ivar1

Şimdi, bu gerçek bir dizi değil, bu bir operatör aşırı yüklemesi; örneğin sizeof (arr) / sizeof (arr [0]) gibi dizilerin normalde yaptığı şeyleri yapmaz. Ama bir dizi referansın tam olarak yasal C ++ ile yapmasını istediğim şeyi yapıyor. (A) 3 veya 4 öğeden fazlasını ayarlamak zor ve (b) bir grup?: Kullanarak bir hesaplama yapmak dışında, indeksleme kullanılarak yapılabilir (normal C-işaretçi-hesaplama-anlamsal indekslemeyle değil , ancak yine de indeksleme). Bunu gerçekten yapabilen çok sınırlı bir 'başvuru dizisi' türü görmek istiyorum. Yani bir referans dizisi, referans olan genel bir şeyler dizisi olarak ele alınmaz, bunun yerine yeni bir 'referans dizisi' olur. şablonlarla yapın).

Eğer bu türden iğrenç olanı aldırmazsanız muhtemelen işe yarar: '* this' dizisini int * 'lerden oluşan bir dizi olarak yeniden oluşturun ve bir:' den yapılan bir referansı döndürür: (önerilmez, ancak doğru 'dizi'nin nasıl olduğunu gösterir. işe yarar):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }

1
Bunu ,std::tuple<int&,int&,int&> abcref = std::tie( a,b,c) yalnızca std :: get kullanarak derleme zamanı sabitleri tarafından indekslenebilen bir referans "dizisi" oluşturan C ++ 11'de yapabilirsiniz. Ama yapamazsın std::array<int&,3> abcrefarr = std::tie( a,b,c). Belki bunu yapmanın bilmediğim bir yolu vardır. Bana öyle geliyor std::array<T&,N>ki , bunu yapabilen bir uzmanlık yazmanın bir yolu olmalı - ve böylelikle std::tiearray(...)böyle birini döndüren (veya std::array<T&,N>= {& v1, & v2 vb} adresleri içeren başlatıcıyı kabul etmeye izin veren)
greggo

Öneriniz aptalca (IMO) ve son satırınız bir int'in bir işaretçi tutabileceğini varsayıyor (genellikle yanlış, en azından çalıştığım yerde). Ancak burada, referans dizilerinin neden yasaklandığına dair geçerli bir gerekçeye sahip tek cevap bu, yani +1
Nemo

@Nemo, gerçekten bir teklif olarak tasarlanmadı, sadece böyle bir şeyin işlevselliğinin yüzünde saçma olmadığını göstermek için. Son yorumumda c ++ 'nın yaklaşan şeylere sahip olduğunu not ediyorum. Ayrıca, başvurduğunuz son satır , bir int referansını uygulayan belleğin int işaretçisi olarak kullanışlı bir şekilde takma ad verilebileceğini varsayar - yapı, ints değil, referanslar içerir - ve bu doğru olma eğilimindedir. Taşınabilir olduğunu iddia etmiyorum (veya 'tanımlanmamış davranış' kurallarından bile güvenli). Tüm ?:
bunlardan

Yeterince adil. Ancak bu fikrin "yüzünde saçma" olduğunu düşünüyorum ve tam da bu yüzden referans dizilerine izin verilmiyor. Bir dizi, her şeyden önce bir kapsayıcıdır. Standart algoritmaların uygulanması için tüm kapsayıcıların bir yineleyici türüne ihtiyacı vardır. "T dizisi" için yineleyici türü normalde "T *" dır. Bir referans dizisi için yineleyici türü ne olabilir? Tüm bu sorunların üstesinden gelmek için gereken dil hackleme miktarı, aslında işe yaramaz bir özellik için saçma. Aslında belki de bunu kendi cevabım yapacağım :-)
Nemo

@Nemo Çekirdek dilin bir parçası olması gerekmez, bu nedenle yineleyici türünün 'özel' olması önemli değildir. Tüm çeşitli konteyner türlerinin yineleyici türleri vardır. Bu durumda başvurulan yineleyici, dizideki başvuruları değil, dizinin davranışıyla tamamen tutarlı olan hedef nesnelere başvurur. Bir şablon olarak desteklemek için hala biraz yeni C ++ davranışı gerektirebilir Çok basit std::array<T,N>, uygulama kodunda kolayca tanımlanabilir, std :: 'ye c ++ 11'e kadar eklenmedi, çünkü muhtemelen buna hiçbir şekilde sahip olmak sersemliktir. do = {1,2,3};işte hack dil 'oldu mu?
greggo

29

Düzenlemenize yorum yapın:

Daha iyi çözüm std::reference_wrapper.

Ayrıntılar: http://www.cplusplus.com/reference/functional/reference_wrapper/

Misal:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    int a=1,b=2,c=3,d=4;
    using intlink = std::reference_wrapper<int>;
    intlink arr[] = {a,b,c,d};
    return 0;
}

Merhaba, bu 09'da tekrar belirtildi. İpucu Yeni gönderilere bak :)
Stígandr

10
Gönderi eski, ancak reference_wrapper ile çözümü herkes bilmiyor. İnsanların C ++ 11'in STL ve STDlib'i hakkında okumaya ihtiyacı var.
2014

Bu, yalnızca sınıfın adını belirtmek yerine bir bağlantı ve örnek kod verir reference_wrapper.
David Stone

13

Bir dizi dolaylı olarak bir işaretçiye dönüştürülebilir ve C ++ 'da işaretçi-referansa yasa dışı


11
Bir referansa bir göstericiniz olamayacağı doğrudur, ancak bir referans dizisine sahip olamamanın nedeni bu değildir. Daha ziyade, her ikisi de referansların nesne olmadığının belirtileridir.
Richard Corden

1
Bir yapı, referanslardan başka hiçbir şey içeremez ve sayıları ile orantılı bir boyuta sahip olacaktır. Eğer bir ref dizisine sahipseniz 'arr', normal diziden işaretçiye dönüşümü bir anlam ifade etmeyecektir, çünkü 'başvuru işaretçisi' mantıklı değildir. ancak arr [i] kullanmak mantıklı olacaktır, tıpkı 'st' bir ref içeren bir yapı olduğunda st.ref gibi. Hmm. Ancak & arr [0], başvurulan ilk nesnenin adresini verirdi ve & arr [1] - & arr [0], & arr [2] - & arr [1] ile aynı olmazdı - çok fazla tuhaflık yaratırdı.
greggo

12

Verilen int& arr[] = {a,b,c,8};nedir sizeof(*arr)?

Diğer her yerde, bir referans sadece şeyin kendisi olarak kabul edilir, bu yüzden sizeof(*arr)basitçe olması gerekir sizeof(int). Ancak bu, bu dizideki dizi işaretçisi aritmetiğini yanlış yapar (referansların aynı genişlikte olmadığını varsayarsak, ints). Belirsizliği ortadan kaldırmak için yasak.


Evet. Bir boyuta sahip olmadığı sürece bir diziye sahip olamazsınız. Bir referansın boyutu nedir?
David Schwartz

5
@DavidSchwartz structTek üyesi bu referans olan bir üye olun ve öğrenin ..?
underscore_d

3
Bu, standartı OP'ye atan aptal bilgiçlik değil, kabul edilen cevap olmalıdır.
Tyson Jacobs

Belki bir yazım hatası vardır. "referansların aynı genişlikte olmadığını varsaymak,", "referansların ints ile aynı genişlikte olmadığını varsayarak" olmalıdır. Değişim olduğu için olduğu gibi .
zhenguoli

Ama bekleyin, bu mantıkla referans üye değişkenlerine sahip olamazsınız.
Tyson Jacobs

10

Çünkü burada birçoklarının söylediği gibi, referanslar nesne değildir. onlar sadece takma adlardır. Doğru, bazı derleyiciler bunları işaretçiler olarak uygulayabilir, ancak standart bunu zorlamaz / belirtmez. Ve referanslar nesne olmadığı için onlara işaret edemezsiniz. Öğeleri bir dizide saklamak, bir tür dizin adresi olduğu anlamına gelir (yani, belirli bir dizindeki öğelere işaret etme); ve bu yüzden referans dizilerine sahip olamazsınız, çünkü onları gösteremezsiniz.

Bunun yerine boost :: reference_wrapper veya boost :: tuple kullanın; ya da sadece işaretçiler.


6

Cevabın çok basit olduğuna ve anlamsal referans kuralları ve dizilerin C ++ 'da nasıl işlendiğiyle ilgili olduğuna inanıyorum.

Kısaca: Referanslar, varsayılan bir kurucuya sahip olmayan yapılar olarak düşünülebilir, dolayısıyla aynı kurallar geçerlidir.

1) Anlamsal olarak, referansların varsayılan bir değeri yoktur. Referanslar sadece bir şeye referans verilerek oluşturulabilir. Referansların referansın yokluğunu temsil edecek bir değeri yoktur.

2) X boyutunda bir dizi ayırırken, program varsayılan olarak başlatılmış nesnelerin bir koleksiyonunu oluşturur. Referansın varsayılan bir değeri olmadığından, böyle bir dizi oluşturmak anlamsal olarak geçersizdir.

Bu kural, varsayılan bir kurucuya sahip olmayan yapılar / sınıflar için de geçerlidir. Aşağıdaki kod örneği derlenmez:

struct Object
{
    Object(int value) { }
};

Object objects[1]; // Error: no appropriate default constructor available

2
Sen bir dizi oluşturabilir Objectsadece hepsini başlatmak için emin olmak zorunda: Object objects[1] = {Object(42)};. Bu arada, hepsini başlatsanız bile bir dizi referans oluşturamazsınız.
Anton3

4

Bu şablon yapısıyla oldukça yakınlaşabilirsiniz. Ancak, T yerine T'ye işaret eden ifadelerle başlatmanız gerekir; ve böylece, benzer şekilde kolayca bir 'sahte_konstref_array' oluşturabilseniz de, bunu OP'nin örneğinde ('8') yapıldığı gibi r değerlerine bağlayamazsınız;

#include <stdio.h>

template<class T, int N> 
struct fake_ref_array {
   T * ptrs[N];
  T & operator [] ( int i ){ return *ptrs[i]; }
};

int A,B,X[3];

void func( int j, int k)
{
  fake_ref_array<int,3> refarr = { &A, &B, &X[1] };
  refarr[j] = k;  // :-) 
   // You could probably make the following work using an overload of + that returns
   // a proxy that overloads *. Still not a real array though, so it would just be
   // stunt programming at that point.
   // *(refarr + j) = k  
}

int
main()
{
    func(1,7);  //B = 7
    func(2,8);     // X[1] = 8
    printf("A=%d B=%d X = {%d,%d,%d}\n", A,B,X[0],X[1],X[2]);
        return 0;
}

-> A = 0 B = 7 X = {0,8,0}


2

Referans nesnenin boyutu yoktur. Eğer yazarsanız , referansın kendisinin değil, sizeof(referenceVariable)tarafından referans verilen nesnenin boyutunu verecektir referenceVariable. Kendi boyutu yoktur, bu nedenle derleyici dizinin ne kadar boyuta ihtiyacı olduğunu hesaplayamaz.


3
eğer öyleyse, o zaman bir derleyici sadece ref içeren bir yapının boyutunu nasıl hesaplayabilir?
Juster

Derleyici, bir referansın fiziksel boyutunu bilir - bir işaretçi ile aynıdır. Referanslar, aslında anlamsal olarak yüceltilmiş işaretçilerdir. İşaretçiler ve referanslar arasındaki fark, işaretçilerle karşılaştırıldığında oluşturabileceğiniz hataların sayısını azaltmak için referansların oldukça az anlamsal kurallara sahip olmasıdır.
Kristupas A.

2

Bir dizide bir şey depoladığınızda, boyutunun bilinmesi gerekir (dizi indeksleme boyuta bağlı olduğundan). C ++ standardına göre Bir referansın depolamaya ihtiyaç duyup duymadığı belirtilmemiştir, sonuç olarak bir referans dizisini indekslemek mümkün olmayacaktır.


1
Ancak söz konusu referansı bir içine koyarak struct, sihirli bir şekilde her şey belirlenmiş, iyi tanımlanmış, garantili ve belirleyici boyutta hale gelir. Öyleyse, görünüşte tamamen yapay olan bu farklılık neden var?
underscore_d

... elbette, eğer biri gerçekten paketlemenin ne getirdiğini düşünmeye başlarsa struct, bazı iyi olası cevaplar kendiliğinden gelmeye başlar - ama bana göre, bu, tüm ortak sorunlardan biri olmasına rağmen, henüz kimse bunu yapmadı. sarmalanmamış referansları neden kullanamayacağınıza dair gerekçeler.
underscore_d

2

Sadece tüm sohbete eklemek için. Diziler, öğeyi depolamak için ardışık bellek konumları gerektirdiğinden, bu nedenle bir dizi referans oluşturursak, bunların ardışık bellek konumunda olacağı garanti edilmez, bu nedenle erişim bir sorun olur ve bu nedenle tüm matematiksel işlemleri uygulayamayız bile. dizi.


Orijinal sorudaki aşağıdaki kodu düşünün: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; A, b, c'nin ardışık bellek konumunda bulunma olasılığı çok düşüktür.
Amit Kumar

Bu, referansları bir kapta tutma konseptiyle ilgili temel sorundan uzaktır.
underscore_d

0

Bir dizi işaretçi düşünün. Bir işaretçi gerçekten bir adrestir; bu nedenle diziyi başlattığınızda, bilgisayara benzer şekilde "bu bellek bloğunu bu X numaralarını (diğer öğelerin adresleri olan) tutması için ayırın" diyorsunuz. Sonra işaretçilerden birini değiştirirseniz, sadece işaret ettiği şeyi değiştirmiş olursunuz; kendisi de aynı noktada oturan sayısal bir adrestir.

Bir referans, bir takma ada benzer. Bir dizi referans bildirecek olsaydınız, temelde bilgisayara "etrafa dağılmış tüm bu farklı öğelerden oluşan bu amorf bellek bloğunu ayırın" diyor olurdunuz.


1
Öyleyse neden structtek üyesi referans olan bir üye yapmak tamamen yasal, öngörülebilir ve amorf olmayan bir şeyle sonuçlanıyor? Bir kullanıcı, sihirli bir şekilde dizi yapabilen güçler elde etmek için neden bir referansı sarmalı, yoksa bu sadece yapay bir kısıtlama mı? IMO hiçbir yanıt doğrudan bu konuya değinmedi. Ben çürütme olduğunu varsayalım 'sarma nesnesi sağlar olabilir size ihtiyaç vardır değerlerin teminat, olabilir sağlar, böylece sarma nesnesi biçiminde, değer anlambilim kullandığından örneğin nasıl olur elemanı ve hakem adresi arasındaki bir belirsizliği' ... Demek istediğin bu muydu?
underscore_d

-2

Aslında bu, C ve C ++ sözdiziminin bir karışımıdır.

Sen gerektiğini ya sadece C ++ parçası olan referans beri, referansların olamaz saf C dizileri kullanın. Veya C ++ yoluna gidin ve amacınız için std::vectorveya std::arraysınıfını kullanın .

Düzenlenen bölüme gelince: structC'den bir öğe olmasına rağmen, onu bir C ++ yapan bir yapıcı ve operatör işlevleri tanımlarsınız class. Sonuç olarak, structsaf C'de derlenemezsiniz!


Ne demek istiyorsun? std::vectorya std::arrayda referans içeremez. C ++ Standardı, hiçbir konteynerin yapamayacağı oldukça açıktır.
underscore_d
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.