참조 배열이 왜 불법입니까?


149

다음 코드는 컴파일되지 않습니다.

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

C ++ 표준은 이것에 대해 무엇을 말합니까?

참조가 포함 된 클래스를 선언 한 다음 아래 표시된 것처럼 해당 클래스의 배열을 만들 수 있다는 것을 알고 있습니다. 그러나 실제로 위의 코드가 컴파일되지 않는 이유를 알고 싶습니다.

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

참조 배열을 시뮬레이션하는 struct cintref대신 사용할 수 있습니다 const int &.


1
배열이 유효하더라도 원시 '8'값을 저장하면 작동하지 않습니다. "intlink value = 8;"을한다면, "const int & value = 8;"로 번역되기 때문에 끔찍하게 죽을 것입니다. 참조는 변수를 참조해야합니다.
그랜트 피터스

3
intlink value = 8;작동합니다. 당신이 믿지 않는지 확인하십시오.
Alexey Malistov

7
Alexey가 지적했듯이 rvalue를 const 참조에 바인딩하는 것이 완벽하게 유효합니다.
avakar

1
무엇을 하지 않는 작품은 즉 operator=. 참조는 다시 설치할 수 없습니다. 당신이 정말로 같은 의미를 원한다면 - 저는 개인적으로 그들이 실질적으로 유용하다있는 상황을 발견 적이 없다하지만 - 후 std::reference_wrapper실제로 포인터를 저장하지만, 레퍼런스와 같이 제공하기 때문에, 그것을 할 수있는 방법이 될 것입니다 operator들과 않습니다 다시 장착 할 수 있습니다. 그러나 나는 단지 포인터를 사용할 것입니다!
underscore_d

1
operator =는 개인용이며 구현되지 않았습니다. 다시 말해 C ++ 11의 내용은 = delete입니다.
Jimmy Hartzell

답변:


148

표준에 대한 귀하의 질문에 대답하면 C ++ 표준 §8.3.2 / 4를 인용 할 수 있습니다 .

참조에 대한 참조, 참조의 배열 및 참조 에 대한 포인터가 없어야합니다.


32
더 이상 할 말이 있습니까?
polyglot

9
포인터 배열, 정보는 동일하지만 처리 방법이 다른 동일한 이유로 참조 배열이 있어야합니까?
neu-rah

6
컴파일러 개발자가 확장으로 참조 배열을 허용하기로 결정했다면 성공했을까요? 아니면이 모호한 코드와 같은 실제 문제가 있습니까?이 확장을 정의하기가 너무 혼란 스럽습니까?
Aaron McDaid

23
나는 @AaronMcDaid 생각 이 이와 유사한 논의에서 너무 눈에 띄게 결석 - - 진짜 이유입니다 하나는 참조의 배열을 가지고 어떻게 요소 및 그 지시 대상의 주소의 주소 사이에 하나 가능성이 명확 것?을 '배열 / 컨테이너 / 무엇이든 참조를 넣을 수 없습니다'에 대한 즉각적인 반대 의견 은 자신의 유일한 구성원이 참조 일 있다는 것 struct입니다. 그러나 그렇게함으로써, 당신은 이제 참조 할 이름과 부모 객체를 둘 다 얻게되었습니다. 이것은 이제 당신이 원하는 주소를 분명하게 진술 할 수 있다는 것을 의미합니다. 분명해 보이는데 ... 왜 아무도 말하지 않았습니까?
underscore_d

95
@polyglot What more is there to say?이유는 무엇 입니까? 나는 표준에 관한 모든 것이지만, 단지 그것을 인용하고 토론의 끝이 사용자의 비판적 사고를 파괴 할 수있는 확실한 방법 인 것 같습니다. 예를 들어 생략 또는 인공 제한. 여기에 '표준이 말했기 때문에'이 너무 많으며 '다음과 같은 실용적인 이유 때문에 그렇게 말하는 것이 합리적입니다'. 나는 왜 upvotes가 후자를 탐색하려고 시도하지 않고 전자에게만 대답하는 답변에 열성적으로 흐르는 지 모르겠습니다.
underscore_d

64

참조는 객체가 아닙니다. 그들은 자신의 저장 공간이 없으며 단지 기존 객체를 참조합니다. 이러한 이유로 참조 배열을 갖는 것은 이치에 맞지 않습니다.

다른 객체를 참조하는 경량 객체 를 원하면 포인터를 사용할 수 있습니다. struct모든 struct인스턴스에 대해 모든 참조 멤버에 대해 명시적인 초기화를 제공하는 경우 배열의 오브젝트로 참조 멤버와 함께 를 사용할 수 있습니다 . 참조는 기본적으로 초기화 할 수 없습니다.

편집 : jia3ep가 지적한 것처럼 선언의 표준 섹션에는 참조 배열에 대한 명시적인 금지가 있습니다.


6
참조는 본질적으로 상수 포인터와 동일하므로 무언가를 가리 키기 위해 메모리 (스토리지)를 차지합니다.
inazaruk

7
필요하지 않습니다. 예를 들어, 로컬 객체에 대한 참조 인 경우 주소 저장을 피할 수 있습니다.
EFraim

4
반드시 그런 것은 아닙니다. 구조의 참조는 일반적으로 저장 공간을 차지합니다. 지역 참조는 종종 그렇지 않습니다. 어느 쪽이든 엄격한 표준 의미에서 참조는 객체가 아니며 명명 된 참조는 실제로 변수 가 아닙니다 .
CB Bailey

26
예, 그러나 이것은 구현 세부 사항입니다. C ++ 모델 의 오브젝트 는 유형이 지정된 스토리지 영역입니다. 참조는 명시 적으로 객체가 아니며 특정 컨텍스트에서 스토리지를 차지한다고 보장하지 않습니다.
CB Bailey

9
이 방법으로 넣으십시오 : foo에 대한 16 개의 참조 만 가질 수있는 구조체를 가질 있으며 16 개의 foo 참조로 색인을 생성 할 수 없다는 점을 제외하고는 참조 배열을 사용하려는 것과 정확히 동일한 방식으로 사용할 수 있습니다 . 이것은 언어 사마귀 IMHO입니다.
greggo

28

이것은 흥미로운 토론입니다. 분명히 일련의 심판은 명백히 불법이지만, IMHO는 왜 '그들은 물체가 없다'거나 '크기가 없다'고 말하는 것처럼 간단하지 않은 이유입니다. 배열 자체는 C / C ++에서 본격적인 객체가 아니라고 지적합니다. 이에 반대하면 배열을 '클래스'템플릿 매개 변수로 사용하여 일부 stl 템플릿 클래스를 인스턴스화하고 어떻게되는지 확인하십시오. 반환, 할당, 매개 변수로 전달할 수 없습니다. (배열 매개 변수는 포인터로 취급됩니다). 그러나 배열 배열을 만드는 것은 합법적입니다. 참조는 컴파일러가 계산할 수 있고 계산해야하는 크기를 갖습니다. 참조를 sizeof () 할 수는 없지만 참조 만 포함하는 구조체를 만들 수 있습니다. 참조를 구현하는 모든 포인터를 포함하기에 충분한 크기를 갖습니다. 넌 할 수있어

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

실제로이 줄을 구조체 정의에 추가 할 수 있습니다

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

... 그리고 이제는 일련의 심판과 같은 모양이 있습니다.

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

자, 이것은 실제 배열이 아니며 연산자 과부하입니다. 예를 들어, 배열이 sizeof (arr) / sizeof (arr [0])과 같이 일반적으로 수행하는 작업은 수행하지 않습니다. 그러나 완벽하게 합법적 인 C ++로 참조 배열을 원하는대로 정확하게 수행합니다. (a) 3 개 또는 4 개 이상의 요소를 설정하는 것이 고통스럽고 (b) 많은?를 사용하여 계산을 수행하고 있습니다. 인덱싱을 사용하여 수행 할 수 있습니다 (일반 C 포인터 계산 시맨틱 인덱싱이 아닌 그럼에도 불구하고 색인 생성). 실제로이 작업을 수행 할 수있는 매우 제한된 '참조 배열'유형을보고 싶습니다. 즉, 참조 배열은 참조 인 일반적인 배열로 취급되지 않고 새로운 '참조 배열'이됩니다. 템플릿으로 작성).

이런 종류의 불쾌감을 신경 쓰지 않으면 아마도 작동 할 것입니다 : int *의 배열로 '* this'를 다시 캐스팅하고 하나의 참조를 반환하십시오 : (권장되지는 않지만 적절한 '배열'을 보여줍니다 작동 할 것이다):

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

1
C ++ 11에서이 작업을 수행 있습니다std::tuple<int&,int&,int&> abcref = std::tie( a,b,c) . std :: get을 사용하여 컴파일 타임 상수로만 인덱싱 할 수있는 참조의 "배열"을 만듭니다. 그러나 당신은 할 수 없습니다 std::array<int&,3> abcrefarr = std::tie( a,b,c). 어쩌면 내가 모르는 방법이있을 수 있습니다. 거기의 전문화 작성하는 방법이 될 것을 나에게 보인다 std::array<T&,N>어떤 있어 기능 - 이렇게 std::tiearray(...)같은 일을 반환 (또는 허용 std::array<T&,N>포함 된 주소 = 초기화 받아 들일 {& V1, V2 등})
greggo

당신의 제안은 바보 (IMO)이며, 마지막 줄은 int가 포인터를 가질 수 있다고 가정합니다 (보통 내가 일하는 곳에서는 거짓). 그러나 이것은 참조의 배열이 금지 된 이유에 대한 정당한 근거를 가진 유일한 대답입니다. 따라서 +1
Nemo

@ Nemo 그것은 실제로 제안으로 의도 된 것이 아니라, 그러한 것의 기능이 얼굴에 터무니없는 것이 아니라는 것을 설명하기 위해서입니다. 나는 마지막 의견에서 c ++에는 가까운 것들이 있다고 언급합니다. 또한 마지막 줄 은 int에 대한 참조 를 구현하는 메모리 가 포인터에 대한 포인터로 유용하게 별칭을 지정할 수 있다고 가정 합니다 (구조에는 int가 아닌 참조가 포함되어 있음). 나는 그것이 휴대용이라고 주장하지 않습니다 (또는 '정의되지 않은 행동'규칙으로부터 안전합니다). 그것은 색인이 모두 피하기 위해 후드 아래 어떻게 사용될 수 있는지 설명하기 위해 의미되었다?:
greggo

그럴 수 있지. 그러나 나는이 아이디어가 "얼굴에 터무니 없다"고 생각하며, 이것이 바로 일련의 참조가 허용되지 않는 이유입니다. 배열은 무엇보다도 컨테이너입니다. 모든 컨테이너에는 표준 알고리즘을 적용하기 위해 반복자 유형이 필요합니다. "T의 배열"에 대한 반복자 유형은 일반적으로 "T *"입니다. 참조 배열의 반복자 유형은 무엇입니까? 이러한 모든 문제를 처리하는 데 걸리는 언어 해킹의 양은 본질적으로 쓸모없는 기능에 대해 말도 안됩니다. 실제로 나는 이것을 내 자신의 답으로 만들 것이다 :-)
Nemo

@Nemo 핵심 언어의 일부일 필요는 없으므로 'custom'인 반복자 유형은 별 문제가되지 않습니다. 모든 다양한 컨테이너 유형에는 반복자 유형이 있습니다. 이 경우 역 참조 된 반복자는 배열의 참조가 아니라 대상 obj를 참조하며, 이는 배열의 동작과 완전히 일치합니다. 여전히 템플릿으로 지원하기 위해 약간의 새로운 C ++ 동작이 필요할 수 있습니다 std::array<T,N>. 앱 코드에서 쉽게 정의 되는 매우 간단 하고 std :: c ++ 11까지 추가되지 않았습니다. 할 일이 = {1,2,3};그 '해킹 언어를'이 되었습니까?
greggo

28

수정 사항에 대한 의견 :

더 나은 해결책은 std::reference_wrapper입니다.

세부 사항 : http://www.cplusplus.com/reference/functional/reference_wrapper/

예:

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

안녕하세요, 이것은 09 년에 다시 언급되었습니다. 팁 더 새로운 게시물 찾기 :)
Stígandr

9
게시물은 오래되었지만 reference_wrapper가있는 솔루션에 대해 모두 알고있는 것은 아닙니다. 사람들은 C ++ 11의 STL 및 STDlib에 대한 읽기 이동이 필요합니다.
Youw

클래스의 이름을 언급하는 대신 링크 및 샘플 코드를 제공합니다 reference_wrapper.
David Stone

12

배열은 암시 적으로 포인터로 변환 가능하며 C ++에서는 포인터 참조가 유효하지 않습니다.


11
참조에 대한 포인터를 가질 수는 없지만 이것이 참조 배열을 가질 수없는 이유는 아닙니다. 오히려 둘 다 참조가 객체가 아니라는 사실의 증상입니다.
Richard Corden

1
구조체는 참조 만 포함 할 수 있으며 그 수에 비례하는 크기를 갖습니다. 참조 'arr'의 배열이있는 경우 '포인터-참조'가 의미가 없으므로 일반적인 배열-포인터 변환은 의미가 없습니다. 그러나 ST ''이 (REF)을 포함하는 구조체이다 st.ref 때와 같은 방식으로, 단지 사용 도착 [I]로 이해한다. 흠. 그러나 & arr [0]은 첫 번째 참조 된 오브젝트의 주소를 제공하며 & arr [1]-& arr [0]은 & arr [2]-& arr [1]과 동일하지 않습니다.
greggo

11

주어진 int& arr[] = {a,b,c,8};것은 무엇 sizeof(*arr)입니까?

다른 곳에서는 참조가 단순히 사물 자체로 취급되므로 sizeof(*arr)간단해야합니다 sizeof(int). 그러나 이렇게하면이 배열에서 배열 포인터 산술이 잘못됩니다 (참조가 같은 너비가 아니라고 가정하면 int입니다). 모호성을 제거하기 위해 금지되어 있습니다.


네. 크기가 없으면 무언가 배열을 가질 수 없습니다. 참고의 크기는 얼마입니까?
David Schwartz

4
@DavidSchwartz 님 struct의 유일한 회원이 아닌 참조를하고 확인하십시오 ..?
underscore_d

3
이것은 OP에서 표준을 던지는 어리석은 pedantic 답변이 아니라 받아 들여지는 대답이어야합니다.
타이슨 제이콥스

어쩌면 오타가있을 수 있습니다. "참조가 동일한 너비가 아니라고 가정하는 것은 정수"라고 가정해야합니다. 변경 입니다 .
zhenguoli

그러나이 논리에 의해 참조 멤버 변수를 가질 수 없었습니다.
타이슨 제이콥스

10

많은 사람들이 여기에서 말했듯이 참조는 객체가 아닙니다. 그들은 단순히 별칭입니다. 실제로 일부 컴파일러는 포인터로 포인터를 구현할 수 있지만 표준에서는 강제로 지정하지 않습니다. 참조는 객체가 아니므로 참조 할 수 없습니다. 배열에 요소를 저장한다는 것은 일종의 인덱스 주소가 있음을 의미합니다 (즉, 특정 인덱스의 요소를 가리킴). 그렇기 때문에 참조 할 수 없기 때문에 참조 배열을 가질 수 없습니다.

대신 boost :: reference_wrapper를 사용하거나 boost :: tuple을 사용하십시오. 또는 그냥 포인터.


5

나는 대답이 매우 간단하고 의미 적 참조 규칙과 배열이 C ++에서 처리되는 방식과 관련이 있다고 생각합니다.

간단히 말하면 : 참조는 기본 생성자가없는 구조체로 생각할 수 있으므로 동일한 규칙이 모두 적용됩니다.

1) 의미 상 참조는 기본값이 없습니다. 참조는 무언가를 참조해야만 만들 수 있습니다. 참조에는 참조가 없음을 나타내는 값이 없습니다.

2) 크기 X의 배열을 할당 할 때 프로그램은 기본 초기화 된 개체의 모음을 만듭니다. 참조에는 기본값이 없으므로 이러한 배열을 만드는 것은 의미 상 불법입니다.

이 규칙은 기본 생성자가없는 구조체 / 클래스에도 적용됩니다. 다음 코드 샘플은 컴파일되지 않습니다.

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

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

1
의 배열을 만들 수 있습니다 . Object모두 초기화해야합니다 Object objects[1] = {Object(42)};. 한편 모든 참조를 초기화하더라도 참조 배열을 만들 수 없습니다.
Anton3

4

이 템플릿 구조체를 사용하면 상당히 가까워 질 수 있습니다. 그러나 T가 아닌 T에 대한 포인터 인 표현식으로 초기화해야합니다. 따라서 유사하게 'fake_constref_array'를 쉽게 만들 수 있지만 OP의 예 ( '8')에서와 같이 rvalue에 바인딩 할 수는 없습니다.

#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

참조 객체는 크기가 없습니다. 을 쓰면 참조 자체 sizeof(referenceVariable)의 크기가 referenceVariable아닌에 의해 참조되는 객체의 크기를 제공 합니다. 자체 크기는 없으므로 컴파일러가 배열에 필요한 크기를 계산할 수 없습니다.


3
그렇다면 컴파일러는 ref 만 포함하는 구조체의 크기를 어떻게 계산할 수 있습니까?
Juster

컴파일러는 참조의 실제 크기 (포인터와 동일)를 알고 있습니다. 실제로 참조는 의미 적으로 영광스럽게 여겨지는 포인터입니다. 포인터와 참조의 차이점은 포인터와 비교할 때 생성 할 수있는 버그 수를 줄이기 위해 참조에 상당히 많은 의미 규칙이 있다는 것입니다.
Kristupas A.

2

배열에 무언가를 저장하는 경우 배열 인덱싱이 크기에 의존하므로 크기를 알아야합니다. C ++ 표준에 따라 참조 배열에 색인을 생성 할 수 없으므로 참조에 스토리지가 필요한지 여부는 지정되어 있지 않습니다.


1
그러나 언급 된 참조를 struct마법 으로 사용 하면 마술처럼 모든 것이 명확하고 명확하게 정의되고 결정적이며 크기가 결정됩니다. 그러므로 왜 이처럼 완전히 인공적인 차이가 존재합니까?
underscore_d

... 물론 래핑이 무엇을 제공하는지 실제로 생각 struct하기 시작하면 몇 가지 좋은 대답이 스스로를 제안하기 시작합니다. 래핑되지 않은 심판을 사용할 수없는 이유에 대한 근거.
underscore_d

2

모든 대화에 추가하십시오. 배열은 아이템을 저장하기 위해 연속적인 메모리 위치를 필요로하기 때문에, 참조 배열을 생성한다면 그것들이 연속적인 메모리 위치에 있다고 보장하지 않으므로 액세스가 문제가 될 수 있으므로 모든 수학적 연산을 적용 할 수도 없습니다. 정렬.


원래 질문에서 아래 코드를 고려하십시오. int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; a, b, c가 연속 메모리 위치에있을 가능성은 매우 적습니다.
Amit Kumar

이는 컨테이너에 참조를 보유한다는 개념의 주요 문제와는 거리가 멀습니다.
underscore_d

0

포인터 배열을 고려하십시오. 포인터는 실제로 주소입니다. 따라서 배열을 초기화 할 때 컴퓨터와 유사하게 "이 메모리 블록을 할당하여이 X 번호 (다른 항목의 주소)를 보유합니다." 그런 다음 포인터 중 하나를 변경하면 가리키는 포인터 만 변경됩니다. 그것은 여전히 ​​그 자리에 같은 숫자 주소입니다.

참조는 별칭과 유사합니다. 참조 배열을 선언하려면 기본적으로 컴퓨터에 "이러한 모든 항목이 흩어져있는이 비정질 메모리를 할당합니다"라고 말합니다.


1
그렇다면 왜 struct유일한 구성원을 기준으로 삼는 것이 합법적이고 예측 가능하며 비정질적인 결과를 가져 오는가? 사용자가 배열 가능 전력을 마술처럼 얻기 위해 참조를 포장해야하는 이유는 무엇입니까? IMO는이 문제를 직접적으로 해결하지 못했습니다. 나는 반박이 '포장 객체는 포장 객체 의 형태로 값 의미론을 사용할 있도록 보장하므로 요소와 심판 주소 사이의 모호한 방법으로 인해 필요한 값을 보장 할 수 있습니다' ... 그게 무슨 뜻입니까?
underscore_d

-2

실제로 이것은 C와 C ++ 구문의 혼합입니다.

당신은해야 하나 만을 ++ C의 일부 참조하기 때문에, 참조 될 수 없다 순수한 C 배열을 사용합니다. 또는 C ++ 방식으로 가서 목적에 따라 std::vectoror std::array클래스를 사용하십시오.

편집 된 부분에 관해서는 : struct가 C의 요소 이지만 생성자와 연산자 함수를 정의하여 C ++로 class만듭니다. 결과적으로 struct순수한 C로 컴파일하지 않을 것입니다!


너의 요점이 뭐야? std::vector또는 std::array참조를 포함 할 수 없습니다. C ++ 표준은 컨테이너가 할 수 없다는 것을 명시 적입니다.
underscore_d
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.