C ++에서 참조가 "const"가 아닌 이유는 무엇입니까?


87

"const 변수"는 일단 할당되면 다음과 같이 변수를 변경할 수 없음을 나타냅니다.

int const i = 1;
i = 2;

위의 프로그램은 컴파일에 실패합니다. gcc가 오류 메시지를 표시합니다.

assignment of read-only variable 'i'

문제 없습니다. 이해할 수 있지만 다음 예는 이해를 초월합니다.

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

그것은 출력

true
false

기묘한. 참조가 이름 / 변수에 바인딩되면이 바인딩을 변경할 수 없으며 바인딩 된 개체를 변경합니다. 나는 가정 그래서의 유형 ri과 동일해야합니다 i때 : i되고 int const, 왜 ri하지 const?


3
또한 boolalpha(cout)매우 드물다. std::cout << boolalpha대신 할 수 있습니다.
isanae

6
그래서 대부분을 ri구별 「별명을 "인 i.
Kaz

1
이는 참조가 항상 동일한 객체에 묶여 있기 때문입니다. i또한 참조이지만 역사적인 이유로 명시적인 방식으로 선언하지 않습니다. 따라서 i스토리지 및ri 참조하는 참조이며 동일한 스토리지를 참조하는 참조입니다. 그러나 그 사이에 본질적으로 차이가 없다 i 하고 ri. 참조의 바인딩을 변경할 수 없기 때문에 const. 그리고 @Kaz 주석이 검증 된 답변보다 훨씬 낫다고 주장하겠습니다 (포인터를 사용하여 참조를 설명하지 마십시오. 참조는 이름이고 ptr은 변수입니다).
장 - 밥 티스트 Yunès

1
환상적인 질문입니다. 이 예를보기 전에이 경우에도 is_const돌아올 것으로 예상했을 것 true입니다. 제 생각에는 이것이 const근본적으로 거꾸로 된 이유에 대한 좋은 예입니다 . "mutable"속성 (a la Rust 's mut)은 더 일관된 것처럼 보입니다.
Kyle Strand

1
제목을 "왜 is_const<int const &>::value거짓입니까?" 로 변경해야 할 수 있습니다. 또는 유사; 유형 특성의 행동에 대해 묻는 것 이외의 질문에 대한 의미를 찾기 위해 고군분투하고 있습니다.
MM

답변:


53

이것은 직관에 반하는 것처럼 보일 수 있지만 이것을 이해하는 방법은 특정 측면에서 참조구문 론적으로 포인터 처럼 취급 .

이것은 포인터에 대해 논리적으로 보입니다 .

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

산출:

true
false

이것은 const 인 포인터 객체 가 아니라 (다른 곳을 가리 키도록 만들 수 있음) 그것이 가리키는 객체 라는 것을 알고 있기 때문에 논리적 입니다.

그래서 우리는 올바르게 constness를포인터 자체 을 false.

우리가 만들고 싶다면 포인터 자체const 말해야합니다.

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

산출:

true
true

그리고 나는 우리가 가진 구문 비유 볼 생각 때문에 참조 .

그러나 참조는 특히에 포인터와 의미 다른 하나 중요한 측면 바인딩 된 후에는 다른 개체에 대한 참조를 다시 바인딩 할 수 없습니다 .

따라서 참조포인터 와 동일한 구문을 공유 하더라도 규칙이 다르므로 언어가 참조 자체 를 선언하지 못하도록 합니다.const 다음과 같이 합니다.

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

언어 규칙이 참조 가 같은 방식으로 리 바인드되는 것을 막을 때 필요하지 않기 때문에 이것을 할 수 없다고 가정 합니다.포인터가 할 수 (선언되지 않은 경우 const).

따라서 질문에 답하려면 :

큐) C ++에서 "참조"가 "상수"가 아닌 이유는 무엇입니까?

귀하의 예제에서 구문 const은 선언하는 경우와 동일한 방식 으로 참조되는 것을 만듭니다 . 포인터를 .

옳든 그르 든 우리는 참조 자체 를 만들 수 const없지만 만약 그렇다면 다음과 같을 것입니다.

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

Q) 참조가 이름 / 변수에 바인딩되면이 바인딩을 변경할 수 없으며 바인딩 된 개체를 변경한다는 것을 알고 있습니다. 따라서 유형은 다음과 ri같아야 한다고 가정합니다 i. when iis a int const, whyri 그렇지 const않습니까?

decltype() 하여 객체에 전송되지 referece가 에 바인딩?

나는 이것이 포인터 와 의미 론적 동등성을위한 decltype()것이라고 생각하고 아마도 (선언 된 유형) 의 기능은 바인딩이 일어나기 전에 선언 된 것을 되돌아 보는 것입니다.


13
"참조는 항상 const이기 때문에 const가 될 수 없다"는 말처럼 들리나요?
ruakh

2
" 바인드 된 후 그들에 대한 의미 상 모든 동작이 그들이 참조하는 객체로 전송된다 "는 것은 사실 일 수 있지만, OP는 그것이 적용되기를 기대하고 있었고 decltype그렇지 않다는 것을 발견했습니다.
ruakh

10
여기에 포인터에 대한 많은 구불 구불 한 가정과 의심스럽게 적용 가능한 비유가 있는데, Sonyuanyao가 한 것과 같은 표준을 확인하면 실제 요점으로 곧바로 도달 할 때 문제를 필요 이상으로 혼동한다고 생각합니다. 참조는 cv 자격이있는 유형이 아니므로 일 수 const없으므로을 std::is_const반환해야합니다 false. 대신 반환해야 함을 의미하는 표현을 사용할 수 true있었지만 그렇지 않았습니다. 그게 다야! 포인터에 관한이 모든 것, "나는 가정한다", "나는 가정한다"등은 어떤 실제적인 설명을 제공하지 않는다.
underscore_d

1
@underscore_d 이것은 아마도 여기 댓글 섹션보다 채팅에 더 좋은 질문 일 것입니다.하지만 레퍼런스에 대한 이런 생각에서 "불필요한 혼란"의 예가 있습니까? 그런 방식으로 구현할 수 있다면 그렇게 생각하면 어떤 문제가 있습니까? 때문에 (나는이 문제의 수를 생각하지 않는다 decltype런타임 작동하지 않은, 정말 적용되지 않습니다 올바른 여부, 아이디어 "런타임에, 참조는 포인터처럼 행동"그래서.
카일 스트랜드

1
참조도 포인터처럼 구문 적으로 처리되지 않습니다. 선언하고 사용할 구문이 다릅니다. 그래서 무슨 말인지 모르겠습니다.
Jordan Melo

55

"ri"가 "const"가 아닌 이유는 무엇입니까?

std::is_const 유형이 const 규정인지 여부를 확인합니다.

T가 const로 한정된 형식 (즉, const 또는 const volatile) 인 경우 멤버 상수 값을 true로 제공합니다. 다른 유형의 경우 값은 false입니다.

그러나 참조는 const로 한정 될 수 없습니다. 참고 문헌 [dcl.ref] / 1

typedef-name ([dcl.typedef], [temp.param]) 또는 decltype-specifier ([dcl.type.simple])를 사용하여 cv 한정자가 도입되는 경우를 제외하고 Cv 한정 참조는 형식이 잘못되었습니다. ,이 경우 cv 한정자는 무시됩니다.

그래서 is_const<decltype(ri)>::value돌아갑니다 falsebecuase ri(참조)는 const를 수식 형식이 아닙니다. 말했듯이 초기화 후에는 참조를 리 바인드 할 수 없습니다. 이는 참조가 항상 "const"임을 의미합니다. 반면에 const 한정 참조 또는 const 비 한정 참조는 실제로 의미가 없을 수 있습니다.


5
받아 들여진 답변의 모든 혼란스러운 가정보다는 표준에 곧바로 따라서 요점에 곧바로.
underscore_d

5
@underscore_d 운영팀이 이유를 묻고 있다는 것을 인식 할 때까지 이것은 좋은 대답입니다. "그렇게 말하고 있기 때문에"는 질문의 해당 측면에 실제로 유용하지 않습니다.
simpleuser

5
@ user9999999 다른 대답은 실제로 그 측면에 대답하지 않습니다. 참조를 리바운드 할 수없는 경우 왜 is_const반환 되지 true않습니까? 이 답변은 포인터가 선택적으로 재 시착 가능한 방식에 대한 비유를 그리는 반면 참조는 그렇지 않습니다. 그렇게함으로써 동일한 이유로 자기 모순으로 이어집니다. 나는 표준을 작성하는 사람들의 다소 임의적 인 결정을 제외하고 어느 쪽이든 실제 설명이 있는지 확신하지 못하며 때로는 그것이 우리가 바랄 수있는 최선의 방법입니다. 따라서이 대답.
underscore_d

2
또 다른 중요한 측면은, 내 생각, 즉 decltype이다 기능하지 그러므로와 직접 참조 자체에서 작동 객체 참조-하기보다는에. (이 대답은 "기본적으로 포인터입니다 참조"를에 아마 더 관련이 있습니다,하지만, 난 여전히 혼란 여기에서 언급 할만한 가치가이 예제를 만드는 것의 그것의 부분을 생각합니다.)
카일 스트랜드

3
@KyleStrand의 주석을 추가로 설명하려면 : decltype(name)일반 decltype(expr). 그래서 예를 들어, decltype(i)의 선언 타입 i이다가있는 const int반면이 decltype((i))될 것이다 int const &.
Daniel Schepler


8

매크로가 아닌 이유는 무엇 const입니까? 기능? 리터럴? 유형의 이름?

const 사물은 불변의 일부일뿐입니다.

참조 유형은 바로 유형이기 때문에 const 다른 유형 (특히 포인터 유형의 경우)과의 대칭을 위해 모두에 대해 -qualifier 일 수 있지만 이것은 매우 지루할 것입니다.

C ++에 기본적으로 변경 불가능한 객체가 있고 원하지 않는mutable 항목에 대한 키워드가 필요 하다면 이것은 쉬웠을 것입니다. 프로그래머 가 참조 유형 에 추가 하는 것을 허용하지 마십시오 .constmutable

그대로, 자격 없이는 불변입니다.

그리고 const정규화 되지 않았기 때문에 참조 유형에서 true를 산출하는 것이 혼란 스러울 것입니다 is_const.

특히 참조를 변경하는 구문이 존재하지 않는다는 단순한 사실에 의해 불변성이 강제되기 때문에 이것이 합리적인 타협이라고 생각합니다.


6

이것은 C ++의 특이한 / 기능입니다. 참조를 유형으로 생각하지 않지만 실제로는 유형 시스템에 "앉아"있습니다. 이것은 어색해 보이지만 (참조가 사용될 때 참조 시맨틱이 자동으로 발생하고 참조가 "비정상적"이라는 점을 감안할 때) 참조가 외부의 별도 속성이 아닌 유형 시스템에서 모델링되는 몇 가지 확실한 이유가 있습니다. 유형.

첫째, 선언 된 이름의 모든 속성이 유형 시스템에 있어야하는 것은 아니라는 점을 고려해 보겠습니다. C 언어에서는 "스토리지 클래스"와 "연결"이 있습니다. 이름은로 도입 될 수 있습니다 extern const int ri. extern여기서은 정적 스토리지 클래스 및 링크의 존재를 나타냅니다. 유형은 단지 const int입니다.

C ++는 표현식이 유형 시스템 외부에있는 속성을 가지고 있다는 개념을 분명히 수용합니다. 이제 언어에는 표현식이 표시 할 수있는 비 유형 속성의 증가하는 수를 구성하려는 시도 인 "값 클래스"개념이 있습니다.

그러나 참조는 유형입니다. 왜?

C ++ 튜토리얼에서는 type을 갖는 것으로 const int &ri소개 된 ri것과 같은 선언이 const int의미론을 참조 한다고 설명했습니다 . 그 참조 의미론은 유형이 아닙니다. 이름과 저장 위치 사이의 비정상적인 관계를 나타내는 일종의 속성 일뿐입니다. 또한 참조가 유형이 아니라는 사실은 유형 생성 구문이 허용하더라도 참조를 기반으로 유형을 생성 할 수없는 이유를 합리화하는 데 사용되었습니다. 예를 들어, 참조에 대한 배열 또는 포인터는 불가능합니다 : const int &ari[5]const int &*pri.

그러나 실제로 참조 유형이므로 decltype(ri)규정되지 않은 일부 참조 유형 노드를 검색합니다. 를 사용하여 기본 유형에 도달하려면 유형 트리에서이 노드를지나 내려 가야합니다 remove_reference.

를 사용 ri하면 참조가 투명하게 확인되므로 ri"모양 및 느낌 i"이되며 "별칭"이라고 할 수 있습니다. 그러나 유형 시스템에서는 ri실제로 " reference to const int "인 유형이 있습니다.

왜 참조 유형입니까?

참조가 유형 이 아니라면 이러한 함수는 동일한 유형을 갖는 것으로 간주됩니다.

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

그것은 거의 자명 한 이유 때문일 수 없습니다. 유형이 같으면 선언이 어느 정의 에나 적합하므로(int) 함수가 참조를받는 것으로 의심되어야합니다.

마찬가지로 참조가 유형이 아닌 경우 다음 두 클래스 선언은 동일합니다.

class foo {
  int m;
};

class foo {
  int &m;
};

한 번역 단위가 하나의 선언을 사용하고 동일한 프로그램의 다른 번역 단위가 다른 선언을 사용하는 것이 옳습니다.

사실 참조는 구현의 차이를 의미하며 C ++의 유형은 엔티티의 구현과 관련이 있기 때문에이를 유형과 분리하는 것은 불가능합니다. 말하자면 비트 단위의 "레이아웃"입니다. 두 함수의 유형이 동일한 경우 동일한 이진 호출 규칙으로 호출 할 수 있습니다. ABI는 동일합니다. 두 구조체 또는 클래스의 유형이 동일한 경우 해당 레이아웃은 모든 멤버에 대한 액세스 의미뿐만 아니라 동일합니다. 참조의 존재는 유형의 이러한 측면을 변경하므로이를 유형 시스템에 통합하는 것은 간단한 디자인 결정입니다. (그러나 여기에서 반론에 유의하십시오. 구조체 / 클래스 멤버는 일 수 있으며 static표현도 변경되지만 유형이 아닙니다!)

따라서 참조는 유형 시스템에서 "두 번째 클래스 시민"으로 존재합니다 (ISO C의 함수 및 배열과 다르지 않음). 참조에 대한 포인터 또는 배열의 선언과 같이 참조로 "할 수없는"특정 작업이 있습니다. 그러나 그것이 그들이 유형이 아니라는 것을 의미하지는 않습니다. 말이되는 방식으로 유형이 아닙니다.

이러한 모든 2 등급 제한이 필수적인 것은 아닙니다. 참조 구조가 있다는 점을 감안할 때 참조 배열이있을 수 있습니다! 예

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

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

이것은 C ++로 구현되지 않은 것뿐입니다. 참조에 대한 포인터는 전혀 의미가 없습니다. 참조에서 들어온 포인터는 참조 된 개체로 이동하기 때문입니다. 참조 배열이없는 이유는 C ++ 사람들이 배열을 C에서 상속 된 일종의 저수준 기능으로 생각하고 복구 할 수없는 여러 가지 방법으로 깨져서 배열을 터치하고 싶지 않기 때문입니다. 새로운 것을위한 기초. 그러나 참조 배열의 존재는 참조가 유형이되어야하는 방법에 대한 명확한 예가 될 것입니다.

const 적격 유형 : ISO C90에도 있습니다!

일부 답변은 참조가 const한정자를 사용 하지 않는다는 사실을 암시합니다 . 선언 const int &ri = i이 정규화 된 참조 를 만들 려고 시도 하지 않기 때문에 오히려 빨간색 청어입니다 const. 이것은 const 정규화 된 유형에 대한 참조입니다 (그 자체가 아닙니다 const). const in *ri무언가에 대한 포인터를 선언하는 것처럼 const포인터 자체는const .

즉, 참고 문헌이 const 한정자를 .

그러나 이것은 그렇게 기괴하지 않습니다. ISO C 90 언어에서도 모든 유형이const . 즉, 배열이 될 수 없습니다.

첫째, const 배열을 선언하기위한 구문이 존재하지 않습니다. int a const [42] .

그러나 위 선언이하려는 것은 중간을 통해 표현할 수 있습니다 typedef.

typedef int array_t[42];
const array_t a;

그러나 이것은 그것이하는 것처럼 보이는 것을하지 않습니다. 이 선언에서 자격 aconst부여 되는 것은 요소가 아니라 요소입니다! 즉, a[0]const int이지만 a"int의 배열"입니다. 따라서 진단이 필요하지 않습니다.

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

이것은 다음을 수행합니다.

a[0] = 1;

다시 말하지만, 이것은 참조가 배열과 같은 유형 시스템에서 어떤 의미에서 "두 번째 클래스"라는 생각을 강조합니다.

배열에도 참조와 같은 "보이지 않는 변환 동작"이 있기 때문에 비유가 훨씬 더 깊이 유지되는 방법에 유의하십시오. 프로그래머가 명시 적 연산자를 사용할 필요가 없으면 식별자 는식이 사용 된 것처럼 a자동으로 int *포인터 로 바뀝니다 &a[0]. 이것은 참조를 ri기본 표현식으로 사용할 때 참조가 마술처럼 객체를 나타내는 것과 유사 합니다.i 가 바인딩 된 를 . "포인터 붕괴에 대한 배열"과 같은 또 다른 "감쇠"입니다.

그리고 "포인터에 대한 배열"이 "배열은 C와 C ++의 포인터 일뿐"이라고 잘못 생각하는 것으로 혼동해서는 안되는 것처럼 참조가 자신의 유형이없는 별칭 일 뿐이라고 생각해서는 안됩니다.

경우 decltype(ri)그 지시 대상 객체에 대한 참조의 일반적인 변환을 억제,이 다르지 아니다 sizeof a어레이 간 포인터 전환을 억제하고, 동작에 어레이 형 자체 의 크기를 계산.


여기에 흥미롭고 유용한 정보 (+1)가 많이 있지만 "참조가 유형 시스템에 있음"이라는 사실에 다소 제한되어 있습니다. 이것은 OP의 질문에 전적으로 대답하지 않습니다. 이 답변과 @songyuanyao의 답변 사이에는 상황을 이해하기에 충분한 정보가 있다고 말하고 싶지만 완전한 답변보다는 답변에 필요한 배경 인 것 같습니다 .
Kyle Strand

1
또한이 문장은 강조 할 가치가 있다고 생각합니다. "리를 사용하면 참조가 투명하게 해결됩니다 ..."한 가지 요점 (댓글에서 언급했지만 지금까지 어떤 답변에도 표시되지 않았습니다. ) 은이 투명한 해상도를 수행 decltype 하지 않는다는 것입니다 (기능 ri이 아니므로 설명하는 의미에서 "사용"되지 않습니다). 에 전체 초점을 아주 멋지게에서이 맞는 타입 시스템 - 그들은 키 연결이 즉이 decltype있다 타입 - 시스템 운영 .
Kyle Strand

흠, 배열에 관한 내용은 접선처럼 느껴지지만 마지막 문장은 도움이됩니다.
Kyle Strand

.....이 시점에서 나는 이미 당신을 upvoted했습니다 당신은 정말 .... 얻고 또는 내 비판을 듣는 것을 잃고하지 않는, 그래서 나는 OP 모르겠지만
카일 스트랜드를

분명히 쉬운 (그리고 정답)은 우리에게 표준을 지적하지만 일부는 그 일부가 가정이라고 주장 할지라도 여기에서 배경 사고를 좋아합니다. +1.
Steve Kidd

-5

const X & x”는 x가 X 개체의 별칭을 의미하지만 x를 통해 해당 X 개체를 변경할 수 없습니다.

그리고 봐라 std :: is_const를.


이것은 질문에 대한 대답을 시도하지도 않습니다.
bolov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.