Mengapa array referensi ilegal?


149

Kode berikut tidak dikompilasi.

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

Apa yang dikatakan standar C ++ tentang ini?

Saya tahu saya bisa mendeklarasikan kelas yang berisi referensi, lalu membuat array kelas itu, seperti yang ditunjukkan di bawah ini. Tetapi saya benar-benar ingin tahu mengapa kode di atas tidak dapat dikompilasi.

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};
}

Dimungkinkan untuk menggunakan struct cintrefalih-alih const int &mensimulasikan array referensi.


1
Bahkan jika array itu valid, menyimpan nilai '8' mentah di dalamnya tidak akan berfungsi. Jika Anda melakukan "intlink value = 8;", itu akan mati mengerikan, karena itu cukup banyak hanya diterjemahkan ke dalam "const int & value = 8;". Referensi harus referensi variabel.
Grant Peters

3
intlink value = 8;tidak bekerja. periksa apakah Anda tidak percaya.
Alexey Malistov

7
Seperti yang ditunjukkan Alexey, sangat sah untuk mengikat nilai ke referensi const.
avakar

1
Yang tidak berhasil adalah itu operator=. Referensi tidak dapat diulang. Jika Anda benar-benar ingin semantik seperti - meskipun saya tidak secara pribadi menemukan situasi di mana mereka praktis berguna - maka std::reference_wrapperakan menjadi cara untuk melakukannya, karena sebenarnya menyimpan pointer tetapi menyediakan referensi-seperti operatordan tidak memungkinkan reseating. Tapi kemudian saya hanya menggunakan pointer!
underscore_d

1
Operator = bersifat pribadi dan tidak diterapkan, alias apa yang ada di C ++ 11 adalah = hapus.
Jimmy Hartzell

Jawaban:


148

Menjawab pertanyaan Anda tentang standar saya dapat mengutip C ++ Standar §8.3.2 / 4 :

Tidak akan ada referensi ke referensi, tidak ada array referensi , dan tidak ada petunjuk ke referensi.


32
Apa lagi yang bisa dikatakan?
polyglot

9
harus ada array referensi untuk alasan yang sama kita memiliki array pointer, informasi yang sama tetapi penanganan berbeda, bukan?
neu-rah

6
Jika pengembang kompiler memutuskan, sebagai perpanjangan, untuk memungkinkan array referensi, maka apakah itu akan berhasil? Atau ada beberapa masalah nyata - mungkin kode ambigu - yang hanya akan membuatnya sangat membingungkan untuk mendefinisikan ekstensi ini?
Aaron McDaid

23
@AaronMcDaid Saya pikir alasan sebenarnya - yang sangat jelas absen dari diskusi ini & yang serupa - adalah bahwa jika seseorang memiliki serangkaian referensi, bagaimana mungkin seseorang akan menyatukan antara alamat suatu elemen & alamat referensi tersebut? Keberatan langsung untuk 'Anda tidak dapat menempatkan referensi dalam array / wadah / apa pun' adalah bahwa Anda dapat menempatkan anggota structsatu-satunya yang menjadi referensi. Tapi kemudian, dengan melakukannya, Anda sekarang punya referensi & objek induk untuk nama ... yang sekarang berarti Anda dapat dengan jelas menyatakan alamat mana yang Anda inginkan. Tampak jelas ... jadi mengapa tidak ada yang mengatakannya?
underscore_d

95
@polyglot What more is there to say?Alasan mengapa ? Saya semua tentang Standar, tetapi hanya mengutipnya dan mengasumsikan bahwa akhir dari diskusi sepertinya adalah cara yang pasti untuk menghancurkan pemikiran kritis pengguna - bersama dengan potensi apa pun untuk bahasa berkembang, misalnya di luar batasan kelalaian atau pembatasan buatan. Ada terlalu banyak 'karena Standar mengatakan' di sini - dan tidak cukup 'dan sangat masuk akal untuk mengatakan itu, karena alasan praktis berikut'. Saya tidak tahu mengapa upvotes mengalir dengan sangat bersemangat ke jawaban yang hanya mengatakan yang pertama tanpa mencoba untuk mengeksplorasi yang terakhir.
underscore_d

64

Referensi bukan objek. Mereka tidak memiliki penyimpanan sendiri, mereka hanya mereferensikan objek yang ada. Untuk alasan ini, tidak masuk akal untuk memiliki array referensi.

Jika Anda ingin ringan objek yang referensi objek lain maka Anda dapat menggunakan pointer. Anda hanya akan dapat menggunakan a structdengan anggota referensi sebagai objek dalam array jika Anda memberikan inisialisasi eksplisit untuk semua anggota referensi untuk semua structinstance. Referensi tidak dapat secara default diinisialisasi.

Sunting: Seperti catatan jia3ep, di bagian standar tentang deklarasi ada larangan eksplisit pada array referensi.


6
Referensi sama dalam sifatnya sebagai pointer konstan, dan oleh karena itu mereka mengambil beberapa memori (penyimpanan) untuk menunjuk ke sesuatu.
inazaruk

7
Tidak perlu. Kompiler mungkin dapat menghindari menyimpan alamat, jika itu referensi ke objek lokal misalnya.
EFraim

4
Belum tentu. Referensi dalam struktur biasanya mengambil beberapa penyimpanan. Referensi lokal seringkali tidak. Either way, dalam pengertian standar yang ketat, referensi bukan objek dan (ini baru bagi saya) yang disebut referensi sebenarnya bukan variabel .
CB Bailey

26
Ya, tetapi ini adalah detail implementasi. Sebuah objek dalam model C ++ merupakan wilayah diketik penyimpanan. Referensi secara eksplisit bukan objek dan tidak ada jaminan bahwa itu mengambil penyimpanan dalam konteks tertentu.
CB Bailey

9
Begini: Anda dapat memiliki struct yang terdiri dari 16 referensi ke foo's, dan menggunakannya dengan cara yang persis sama seperti saya ingin menggunakan berbagai referensi saya - kecuali bahwa Anda tidak dapat mengindeks ke dalam 16 referensi foo . Ini adalah IMHO bahasa kutil.
greggo

28

Ini diskusi yang menarik. Array yang jelas jelas ilegal, tetapi IMHO alasan mengapa tidak sesederhana mengatakan 'mereka bukan benda' atau 'mereka tidak memiliki ukuran'. Saya akan menunjukkan bahwa array itu sendiri bukan objek yang lengkap dalam C / C ++ - jika Anda keberatan, coba instantiating beberapa kelas template stl menggunakan array sebagai parameter template 'class', dan lihat apa yang terjadi. Anda tidak dapat mengembalikannya, menetapkannya, meneruskannya sebagai parameter. (param array diperlakukan sebagai pointer). Tetapi adalah legal untuk membuat array dari array. Referensi memang memiliki ukuran yang dapat dan harus dihitung oleh kompilator - Anda tidak dapat sizeof () referensi, tetapi Anda dapat membuat struct yang hanya berisi referensi. Ini akan memiliki ukuran yang cukup untuk memuat semua pointer yang mengimplementasikan referensi. Kamu bisa'

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

Bahkan Anda dapat menambahkan baris ini ke definisi struct

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

... dan sekarang saya memiliki sesuatu yang terlihat BANYAK seperti serangkaian referensi:

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

Sekarang, ini bukan array nyata, ini kelebihan operator; itu tidak akan melakukan hal-hal yang biasanya dilakukan array seperti sizeof (arr) / sizeof (arr [0]), misalnya. Tapi itu tidak persis apa yang saya ingin array referensi lakukan, dengan C ++ legal. Kecuali (a) itu sulit untuk mengatur lebih dari 3 atau 4 elemen, dan (b) melakukan perhitungan menggunakan sekelompok? , tetapi tetap mengindeks). Saya ingin melihat tipe 'array referensi' yang sangat terbatas yang sebenarnya dapat melakukan ini. Yaitu array referensi tidak akan diperlakukan sebagai array umum dari hal-hal yang merupakan referensi, tetapi akan menjadi 'array-referensi-baru' buat dengan templat).

ini mungkin akan berhasil, jika Anda tidak keberatan dengan jenis jahat ini: menyusun kembali '* this' sebagai array int * dan mengembalikan referensi yang dibuat dari satu: (tidak disarankan, tetapi ini menunjukkan bagaimana 'array' yang tepat) akan bekerja):

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

1
Anda dapat melakukan ini di C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)- yang menciptakan "array" referensi yang hanya dapat diindeks oleh konstanta kompilasi-waktu menggunakan std :: get. Tapi kamu tidak bisa melakukannya std::array<int&,3> abcrefarr = std::tie( a,b,c). Mungkin ada cara untuk melakukannya yang tidak saya ketahui. Sepertinya saya harus ada cara untuk menulis spesialisasi std::array<T&,N>yang dapat melakukan ini - dan dengan demikian fungsi std::tiearray(...)yang mengembalikan satu seperti itu (atau memungkinkan std::array<T&,N>untuk menerima inisialisasi yang berisi alamat = {& v1, & v2 dll})
greggo

Proposal Anda konyol (IMO), dan baris terakhir Anda mengasumsikan int dapat menyimpan pointer (biasanya false, setidaknya di tempat saya bekerja). Tapi ini adalah satu-satunya jawaban di sini dengan alasan yang sah mengapa array referensi dilarang, jadi +1
Nemo

@Nemo itu tidak benar-benar dimaksudkan sebagai proposal, tetapi hanya untuk menggambarkan bahwa fungsi hal semacam itu tidak masuk akal di wajahnya. Saya perhatikan dalam komentar terakhir saya bahwa c ++ memiliki hal-hal yang mendekati. Juga, baris terakhir yang Anda rujuk berasumsi bahwa memori yang mengimplementasikan referensi ke int dapat bermanfaat sebagai pointer-to-int - struct berisi referensi, bukan int - dan yang cenderung benar. Saya tidak mengklaim itu portabel (atau bahkan aman dari aturan 'perilaku tidak terdefinisi'). itu dimaksudkan untuk menggambarkan bagaimana pengindeksan dapat digunakan di bawah kap untuk menghindari semua?:
greggo

Cukup adil. Tapi saya pikir ide ini "absurd di wajahnya", dan inilah mengapa array referensi tidak diizinkan. Array adalah, pertama dan terpenting, sebuah wadah. Semua kontainer memerlukan jenis iterator untuk diterapkan algoritma standar. Jenis iterator untuk "array T" biasanya "T *". Apa yang akan menjadi tipe iterator untuk array referensi? Jumlah peretasan bahasa yang diperlukan untuk menangani semua masalah ini konyol untuk fitur yang pada dasarnya tidak berguna. Sebenarnya mungkin saya akan membuat ini jawaban saya sendiri :-)
Nemo

@Nemo Tidak perlu menjadi bagian dari bahasa inti, jadi tipe iterator yang menjadi 'kebiasaan' bukan masalah besar. Semua berbagai jenis wadah memiliki jenis iterator mereka. Dalam hal ini iterator, dereferenced, akan mereferensikan objek target, bukan referensi dalam array, yang sepenuhnya konsisten dengan perilaku array. Mungkin masih memerlukan sedikit perilaku C ++ baru untuk mendukung sebagai templat. Sangat sederhana std::array<T,N>, mudah didefinisikan dalam kode aplikasi, tidak ditambahkan ke std :: sampai c ++ 11, mungkin karena berkutat untuk memiliki ini tanpa cara untuk do = {1,2,3};Apakah itu 'bahasa hacker'?
Greggo

28

Komentar untuk hasil edit Anda:

Solusi yang lebih baik adalah std::reference_wrapper.

Detail: http://www.cplusplus.com/reference/functional/reference_wrapper/

Contoh:

#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;
}

Hai ini dinyatakan kembali pada 09. Kiat mencari posting yang lebih baru :)
Stígandr

9
Posting sudah lama tetapi tidak semua orang tahu tentang solusi dengan reference_wrapper. Orang perlu membaca langkah tentang STL dan STDlib dari C ++ 11.
Youw

Ini memberikan tautan dan kode sampel, alih-alih hanya menyebutkan nama kelas reference_wrapper.
David Stone

12

Array secara implisit dapat dikonversi menjadi pointer, dan pointer-to-reference ilegal di C ++


11
Memang benar Anda tidak dapat memiliki pointer ke referensi, tetapi ini bukan alasan bahwa Anda tidak dapat memiliki array referensi. Mereka berdua adalah gejala dari fakta bahwa referensi bukanlah objek.
Richard Corden

1
Str dapat berisi apa-apa selain referensi, dan itu akan memiliki ukuran yang proporsional dengan jumlah mereka. Jika Anda memang memiliki array 'ar' referensi, konversi array-ke-pointer normal tidak masuk akal, karena 'pointer ke referensi' tidak masuk akal. tetapi masuk akal untuk hanya menggunakan arr [i], dengan cara yang sama seperti st.ref ketika 'st' adalah struct yang mengandung ref. Hmm. Tetapi & arr [0] akan memberikan alamat objek yang direferensikan pertama, dan & arr [1] - & arr [0] tidak akan sama dengan & arr [2] - & arr [1] - itu akan menciptakan banyak keanehan.
greggo

11

Diberikan int& arr[] = {a,b,c,8}; , apa itu sizeof(*arr)?

Di tempat lain, referensi diperlakukan hanya sebagai hal itu sendiri, jadi sizeof(*arr)seharusnya begitu saja sizeof(int). Tapi ini akan membuat array pointer aritmatika pada array ini salah (dengan asumsi referensi yang lebar tidak sama dengan int). Untuk menghilangkan ambiguitas, itu dilarang.


Ya. Anda tidak dapat memiliki array sesuatu kecuali memiliki ukuran. Berapa ukuran referensi?
David Schwartz

4
@DavidSchwartz Membuat anggota structyang hanya memiliki referensi itu dan mencari tahu ..?
underscore_d

3
Ini harus menjadi jawaban yang diterima, bukan jawaban pedantic bodoh yang hanya melempar standar ke OP.
Tyson Jacobs

Mungkin ada kesalahan ketik. "dengan asumsi bahwa referensi tidak sama lebar dengan int" seharusnya "dengan asumsi bahwa referensi tidak sama lebar dengan int". Perubahan adalah untuk sebagai .
zhenguoli

Tetapi tunggu, dengan logika ini Anda tidak dapat memiliki variabel anggota referensi.
Tyson Jacobs

10

Karena seperti yang banyak dikatakan di sini, referensi bukanlah objek. mereka hanyalah alias. Benar beberapa kompiler mungkin mengimplementasikannya sebagai pointer, tetapi standar tidak memaksa / menentukan itu. Dan karena referensi bukan objek, Anda tidak dapat mengarahkannya. Menyimpan elemen dalam array berarti ada beberapa jenis alamat indeks (yaitu, menunjuk ke elemen pada indeks tertentu); dan itulah sebabnya Anda tidak dapat memiliki array referensi, karena Anda tidak dapat mengarahkannya.

Gunakan boost :: reference_wrapper, atau boost :: tuple sebagai gantinya; atau hanya petunjuk.


5

Saya percaya jawabannya sangat sederhana dan ada hubungannya dengan aturan referensi semantik dan bagaimana array ditangani dalam C ++.

Singkatnya: Referensi dapat dianggap sebagai struct yang tidak memiliki konstruktor default, jadi semua aturan yang sama berlaku.

1) Secara semantik, referensi tidak memiliki nilai default. Referensi hanya dapat dibuat dengan merujuk sesuatu. Referensi tidak memiliki nilai untuk mewakili tidak adanya referensi.

2) Saat mengalokasikan array ukuran X, program membuat koleksi objek yang diinisialisasi default. Karena referensi tidak memiliki nilai default, membuat array seperti itu secara semantik ilegal.

Aturan ini juga berlaku untuk struct / kelas yang tidak memiliki konstruktor default. Contoh kode berikut tidak dikompilasi:

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

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

1
Anda dapat membuat sebuah array Object, Anda hanya perlu memastikan untuk menginisialisasi mereka semua: Object objects[1] = {Object(42)};. Sementara itu, Anda tidak dapat membuat array referensi, bahkan jika Anda menginisialisasi semuanya.
Anton3

4

Anda bisa cukup dekat dengan struct template ini. Namun, Anda perlu menginisialisasi dengan ekspresi yang mengarah ke T, bukan T; jadi, meskipun Anda dapat dengan mudah membuat 'fake_constref_array' dengan cara yang sama, Anda tidak akan dapat mengikatnya ke nilai seperti yang dilakukan dalam contoh OP ('8');

#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

Objek referensi tidak memiliki ukuran. Jika Anda menulis sizeof(referenceVariable), itu akan memberi Anda ukuran objek yang dirujuk referenceVariable, bukan referensi itu sendiri. Ia tidak memiliki ukuran sendiri, itulah sebabnya kompiler tidak dapat menghitung berapa ukuran array yang diperlukan.


3
jika demikian bagaimana kemudian kompiler dapat menghitung ukuran struct yang hanya berisi referensi?
Juster

Compiler memang tahu ukuran fisik referensi - sama dengan pointer. Referensi sebenarnya adalah petunjuk yang dimuliakan secara semantik. Perbedaan antara pointer dan referensi adalah bahwa referensi memiliki beberapa aturan semantik yang ditempatkan padanya untuk mengurangi jumlah bug yang mungkin Anda buat dibandingkan dengan pointer.
Kristupas A.

2

Ketika Anda menyimpan sesuatu dalam sebuah array, ukurannya perlu diketahui (karena pengindeksan array bergantung pada ukurannya). Per standar C ++ Tidak ditentukan apakah referensi membutuhkan penyimpanan atau tidak, karena itu pengindeksan array referensi tidak mungkin dilakukan.


1
Tetapi dengan menempatkan referensi tersebut dalam struct, secara ajaib semuanya menjadi ditentukan, didefinisikan dengan baik, dijamin, dan ukuran deterministik. Karena itu, mengapa perbedaan yang tampaknya sepenuhnya buatan ini ada?
underscore_d

... tentu saja, jika seseorang benar-benar mulai berpikir tentang apa yang structdiberikan oleh pembungkus itu , beberapa jawaban yang baik mulai menunjukkan diri mereka sendiri - tetapi bagi saya, belum ada yang melakukannya, meskipun ini menjadi salah satu tantangan langsung bagi semua orang. alasan mengapa Anda tidak dapat menggunakan referensi yang tidak terbungkus.
underscore_d

2

Hanya untuk menambah semua percakapan. Karena array memerlukan lokasi memori berturut-turut untuk menyimpan item, jadi jika kita membuat array referensi maka tidak dijamin bahwa mereka akan berada di lokasi memori berturut-turut sehingga mengakses akan menjadi masalah dan karenanya kita bahkan tidak dapat menerapkan semua operasi matematika pada Himpunan.


Pertimbangkan kode di bawah ini dari pertanyaan awal: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; Sangat kecil kemungkinan a, b, c akan berada di lokasi memori berurutan.
Amit Kumar

Itu jauh dari masalah utama dengan konsep memegang referensi dalam sebuah wadah.
underscore_d

0

Pertimbangkan berbagai petunjuk. Pointer adalah alamat; jadi ketika Anda menginisialisasi array, Anda memberi tahu komputer secara analog, "alokasikan blok memori ini untuk menampung angka-angka X ini (yang merupakan alamat barang-barang lainnya)." Kemudian jika Anda mengubah salah satu dari pointer, Anda hanya mengubah apa yang ditunjukkannya; itu masih alamat numerik yang dengan sendirinya duduk di tempat yang sama.

Referensi analog dengan alias. Jika Anda mendeklarasikan serangkaian referensi, Anda pada dasarnya akan memberi tahu komputer, "alokasikan gumpalan memori amorf ini yang terdiri dari semua item berbeda yang tersebar di sekitarnya."


1
Lalu, mengapa membuat satu struct-satunya anggotanya yang menjadi referensi menghasilkan sesuatu yang sepenuhnya legal, dapat diprediksi, dan tidak amorf? Mengapa pengguna harus membungkus referensi agar dapat secara ajaib mendapatkan kekuatan array, atau itu hanya pembatasan buatan? IMO tidak ada jawaban yang langsung membahas hal ini. Saya berasumsi bantahannya adalah 'Objek pembungkus memastikan Anda dapat menggunakan semantik nilai, dalam bentuk objek pembungkus, sehingga memastikan Anda dapat memiliki jaminan nilai, yang diperlukan karena misalnya bagaimana seseorang akan ambigu antara elemen dan alamat wasit' ... Apakah itu yang kamu maksud?
underscore_d

-2

Sebenarnya, ini adalah campuran dari sintaks C dan C ++.

Anda harus baik menggunakan array C murni, yang tidak dapat referensi, karena referensi merupakan bagian dari C ++ saja. Atau Anda menggunakan cara C ++ dan menggunakan kelas std::vectoratau std::arrayuntuk tujuan Anda.

Adapun bagian yang diedit: Meskipun structmerupakan elemen dari C, Anda mendefinisikan fungsi konstruktor dan operator, yang membuatnya menjadi C ++ class. Akibatnya, Anda structtidak akan dikompilasi dalam C murni!


Apa maksudmu std::vectoratau std::arraytidak dapat memuat referensi. Standar C ++ cukup eksplisit sehingga tidak ada wadah yang bisa.
underscore_d
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.