OneDev

[C++] 참조자(레퍼런스) 본문

Language/C++

[C++] 참조자(레퍼런스)

one_dev 2022. 8. 5. 21:57

1. 참조자(레퍼런스)란?

C 언어에서는 어떤 변수를 가리키고자 할 때 포인터를 사용해야 했다.

C++ 에서는어떤 변수나  상수를 가리키는 또다른 방법을 제공하는데, 이를 참조자(혹은 레퍼런스) 라 한다.

레퍼런스(reference : 참조) 라는 이름에서 알 수 있듯이 참조의 의미를 가지는 자료구조이다.

 

2. 레퍼런스 변수의 선언

참조자(레퍼런스) 의 선언 &연산자를 이용한다.

  • 형식 : (자료형)& (참조자 이름);
  • 선언과 동시에 어떤 변수와 초기화 할것인지 명시함
  • 나중에 다른 함수를 참조하도록 변경 불가

 

 

 

변수 선언이 메모리 할당이었다면, 레퍼런스의 선언은 어떤 변수를 참조할지에 대한 선언이라 보면 된다.

위의 사진에서 우리는 int& another_a = a; 라고 적어줌으로써 another_a 는 a 의 참조자임을 공표하게 되었다.

즉, another_a 가 a 의 또다른 이름 (혹은 별명) 이라고 컴파일러에게 알려주게 된것이다.

참조자 에게 어떤작업을 수행하든 참조자가 가리키는 변수 a 에 작업을 수행하는 것이나 마찬가지가 된다.

 

3. 포인터와 참조자의 차이점?

포인터와 참조자는 어떤 대상을 가리키는 구조라는 점에서 유사하다.

포인터 역시 다른 어떤 변수의 주소값을 보관함으로써 해당 변수에 간접적으로 연산을 수행할 수 있다.

하지만 포인터와 레퍼런스 사이에는 몇가지 중요한 차이점이 있다.

 

(1) 레퍼런스를 정의할 때는 반드시 처음에 누구의 별명이 될 지 명시해줘야 한다.

int& another_a; // 이렇게 선언만 하는 것은 불가능!

반면 포인터는 선언만 해줘도 문제가 없다.

int *p; // 아무 문제 없다

 

(2) 레퍼런스를 한번 정의하면 더이상 다른 변수를 참조할 수 없다.

 

즉, 별명을 한번 붙여주면 이 별명은 무덤까지 가지고 간다는것이다(무섭다....).

int a = 10;  // int형 변수 a 의 값은 10
int& another_a =a ; // 이제 another_a 는 a 의 참조자

int b = 3;  // int형 변수 b 의 값은 3
another_a = b;  // ???

위의 예시 코드 마지막줄의 another_a = b; 는 무슨뜻일까? another_a 더러 b 를 참조하라는 뜻일까?

레퍼런스를 한번 정의한 이상 another_a 는 a 외에 다른 변수를 참조할 수 없다.

another_a = b; 라는 코드는 앞서 말했듯이 a = b; 와 완전히 동일한 코드인 것이다.

 

반면 포인터는 가리키는 대상을 자유롭게 바꿀 수 있다.

int a = 10; 
int* p = a;  // p는 a 를 가리킨다

int b = 5;
int* p = b;  // 이제 p 는 a 를 버리고 b 를 가리킨다!

 

 

(3) 레퍼런스는 메모리 상에 존재하지 않을 수 도 있다

int a = 1;
int *p = a; // p는 메모리 상에서 당당하게 8바이트를 차지하게 되었다(포인터도 변수!)

어쨌거나 포인터도 변수이기 때문에 메모리 상에서 공간을 차지한다.

(64비트 시스템에서는 8바이트, 32비트 시스템에서는 4바이트가 될것이다)

 

그렇다면 레퍼런스의 경우는 어떨까?

int a = 10;
int& another_a = a;   // 참조자의 경우는 어떨까?

결론부터 말하면 레퍼런스는 메모리 상에 존재하지 않을 수도 있고 존재할 수도 있다.

위의 예시코드의 경우 another_a 가 쓰일 때마다 a 로 바꿔치기 해주면 되기 때문에 굳이 메모리 상에 공간을 할당해줄 필요가 없다. ( 그럼 메모리 상에 존재하는 경우는 어떤 경우일까?)

 

4.레퍼런스의 배열 vs 배열의 레퍼런스 ?

(1) 레퍼런스의 배열?

결론부터 말하면 레퍼런스의 배열은 불가능(정확히는 illegal) 하다. (참고 : C++ 규정 8.3.2/4)

배열의 이름이 배열의 첫 번째 원소의 주소값이라는 것은 많은 분들이 아실거라 생각한다.

주소값이 존재한다는 것은, 해당 원소가 메모리 상에 존재한다는 의미인데 

레퍼런스는 특수한 경우가 아닌 이상 별도의 메모리를 차지하지 않는다.

이러한 모순때문에 C 언어차원에서 레퍼런스의 배열을 막아놓았다.

 

(2) 배열의 레퍼런스

레퍼런스의 배열은 불가능하지만, 배열의 레퍼런스는 가능하다.

먼저 아래의 예시를 보자.

#include <iostream>

using namespace std;

int main() {
	int arr[3] = { 1,2,3 };
	int(&ref)[3] = arr;

	ref[0] = 2;
	ref[1] = 3;
	ref[2] = 1;

	cout << arr[0] << arr[1] << arr[2] <<  endl;

	return 0;
}

 

위의 코드를 실행하면 다음과 같은 결과를 얻을 수 있다 :

 

어떻게 이런 결과가 나온것일까?

int arr[3] = { 1,2,3 };
int(&ref)[3] = arr;

우리는 ref 가 arr 을 참조하도록 하였다.

그 결과 ref[0] 는 arr[0]  의 참조자, ref[1] 은 arr[1] 의 참조자, 그리고 ref[2] 는 arr[2]의 참조자가 된 것이다.

포인터와는 다르게 배열의 레퍼런스의 경우 참조하기 위해서는 반드시 배열의 크기를 명시해주어야 한다.

 

 

5. 댕글링(Dangling) 레퍼런스

댕글링 레퍼런스(Dangling reference) 란 레퍼런스는 있는데 원래 참조하던 것이 사라진 레퍼런스를 말한다.

(참고 : Dangling = 달랑달랑거리는)

레퍼런스가 참조해야할 변수가 사라져 혼자 덩그러니 남아있는 상황과 유사하다고 생각하면 된다.

 

아래의 예시를 보자

int& function() {
  int a = 2;
  return a;
}

int main() {
  int b = function();
  b = 3;
  return 0;
}

정의된 함수 function의 리턴값은 int& 이고, 따라서 참조자를 리턴하게 된다.

문제는 리턴하는 function 안의 a 는 함수의 리턴과 함께 사라진다는 점이다.

main 함수에서 function 안에 정의된 a 라는 값이 b 라는 변수에 복사되었지만 function의 종료와 동시에 a는 사라지기 때문에 오류가 발생하게 된다.

위처럼 레퍼런스를 리턴하는 함수에서 지역변수의 레퍼런스를 리턴하지 않도록 주의해야 한다.

'Language > C++' 카테고리의 다른 글

[C++] new 와 delete : 메모리 할당과 해제  (0) 2022.08.09
[C++] 자료형 개요(작성중)  (0) 2022.08.01
[C++] main 함수란?  (0) 2022.08.01
[C++] 변수 선언  (0) 2022.07.04
[C++] 이름공간(namespace)  (0) 2022.07.04
Comments