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

윤정민·2023년 6월 29일
0

C++

목록 보기
7/46

1. 참조자(레퍼런스)

참조자: 별명, 즉 또다른 이름으로 반드시 정의 시 누구의 별명이 될 것인지 지정해야 한다.(한번 별명이 되면 절대로 다른 이의 별명이 될 수 없음)

  • 레퍼런스는 메모리 공간 속에 존재하지 않을 수 있음
    • 레퍼런스를 위해 새로운 공간을 할당할 필요가 없을 수도 있음
  • 레퍼런스(reference)는 &기호를 이용해서 표기
#include <iostream>

int main() {
    int a = 3;
    int& another_a = a;
}

2. 참조자의 참조자

int a = 1;
int& b = a;
int& c = b;

c = 2;
  • 위의 경우에, "c=2"를 실행하면 a,b,c의 값이 전부 2로 바뀜
    • 세 변수 모두 같은 곳을 가리키고 있기 때문
  • cb를 가리키기 위해 &&같은 식으로 사용할 필요가 없음

3. 상수에 대한 참조자(에러)

  • 아래 코드를 컴파일 해보면 오류가 나타남
    • 상수 값 자체는 리터럴이기 때문에 레퍼런스로 참조를 한다면 리터럴의 값을 바꾸는 행위가 가능해지기 때문
#include <iostream>

int main() {
  int &ref = 4;

  std::cout << ref << std::endl;
}
  • 상수 참조자로 선언한다면 리터럴도 참조 가능
const int &ref = 4;
int a = ref;

4. 레퍼런스의 배열과 배열의 레퍼런스(오류)

  • 아래 코드를 컴파일 해보면 오류가 나타남

    c++에서 배열을 처리하는 방식을 생각해보면 배열의 이름은 첫 번째 원소의 주소값으로 변환이 될 수 있어야하기때문에 arr[1]*(arr+1)로 변환가능하다. 이때 주소값이 존재한다는 의미는 해당 원소가 메모리 상에서 존재한다는 의미이기 때문에 레퍼런스는 특별한 경우가 아닌 이상 메모리 상에 존재하지 않으니 레퍼런스들의 배열을 정의하는 것은 모순이다.

int a, b;
int& arr[2] = {a,b};
  • 배열들의 레퍼런스는 가능
    • 반드시 배열의 크기를 명시해야 함
#include <iostream>

int main() {
  int arr[3] = {1, 2, 3};
  int(&ref)[3] = arr;  //ref[0]부터 ref[2]가 각각 arr[0]부터 arr[2]의 레퍼런스가 됨

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

  std::cout << arr[0] << arr[1] << arr[2] << std::endl;
  return 0;
}

5. 레퍼런스를 리턴하는 함수

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

int main() {
  int b = function();
  return 0;
}
  • function안에 정의된 a라는 변수의 값이 b에 복사 됨
  • function이 종료되고 나면 a는 메모리에서 사라짐

5.1. 지역 변수의 레퍼런스를 리턴

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

int main() {
  int b = function();
  b = 3;
  return 0;
}
  • 해당 코드를 컴파일 한다면 경고가 나옴(오류는 아님)
    • 실행시 런타임 오류가 발생
      • function안에 정의된 a의 참조자를 리턴하게 되는데 function이 종료됨과 동시에 a또한 사라지니 런타임 오류가 발생

5.2. 외부 변수의 레퍼런스를 리턴

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

int main()
{
  int b = 2;
  int c = function(b);
  return 0;
}
  • 해당 코드도 function이 레퍼런스를 리턴하고 있음
    • 인자로 받은 레퍼런스를 그대로 리턴
      • function(b)를 실행한 시점에서 amainb를 참조해 아직 살아있는 b를 계속 참조하게 됨
      • 따라서 int c = function(b);c에 현재의 b값인 5를 대입하는 것과 동일

5.3. 참조자를 리턴하는 경우 장점

  • c언어 에서 매우 큰 구조체가 있을 때 해당 구조체 변수를 그냥 리턴하면 전체 복사가 발생해 시간이 오래 걸리지만, 해당 구조체를 가리키는 포인터를 리턴한다면 그냥 포인터 주소 한번 복사로 매우 빠르게 끝남
  • 레퍼런스 또한 마찬가지로 레퍼런스가 참조하는 타입의 크기와 상관없이 딱 한번의 주소값 복사로 전달이 끝남

5.4. 참조자가 아닌 리턴 값을 참조자로 받기

int function()
{
  int a = 5;
  return a;
}

int main()
{
  int& c = function();
  return 0;
}
  • 위 코드를 컴파일 하게 되면 상수가 아닌 레퍼런스가 function 함수의 리턴값을 참조할 수 없다는 오류가 발생

    • 함수의 리턴값은 해당 문장이 끝난 후 사라지는 값이기 때문에 참조자를 만들게 되면 바로 다음에 댕글링 레퍼런스가 되버리기 때문
  • 예외

    int function()
    {
      int a = 5;
      return a;
    }
    
    int main()
    {
      const int& c = function();
      std::cout<<"c : "<< c <<std::endl;
      return 0;
    }
    • 위 코드를 실행하게 되면 정상적으로 작동함
      • 원칙상 함수의 리턴값은 해당 문장이 끝나면 소멸되는 것이 정상이지만 예외적으로 상수 레퍼런스로 리턴값을 받게 되면 해당 리턴값의 생명이 연장
        • 연장기간은 레퍼런스가 사라질 때 까지

6. 정리

함수에서 값 리턴(int f())함수에서 참조자 리턴(int& f())
값 타입으로 받음int a = f()값 복사됨값이 복사되지만 지역 변수의 레퍼런스를 리턴하지 않도록 주의
참조자 타입으로 받음int& a = f()컴파일 오류가능하지만 지역 변수의 레퍼런스를 리턴하지 않도록 주의
상수 참조자 타입으로 받음const int& a = f()가능가능 하지만 지역 변수의 레퍼런스를 리턴하지 않도록 주의

7. Discussion

Q1. 레퍼런스가 메모리 상에 반드시 존재해야 하는 경우는 어떤 경우일까?

  • A1. 일반적으로 레퍼런스는 그저 별칭이기 때문에 메모리상에 존재하지 않음
    Q2. 그리고 메모리 상에 존재할 필요가 없는 경우는 또 어떤 경우일까?
  • A2. 함수 인자로 사용되어 값을 전달받게 되면 메모리가 할당됨
    • call by reference를 하게 되면 참조자를 받을 때 주소를 전달하기 위해 메모리가 사용됨(함수에 넘겨주기 위해 메모리상에 존재하게 됨!)
profile
그냥 하자

0개의 댓글