Notice
Recent Posts
Recent Comments
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 02-4 : 함수에서의 참조자(Reference)(2) 본문

Programming Lang/C++

C++ Chapter 02-4 : 함수에서의 참조자(Reference)(2)

시데브 2023. 7. 11. 14:47

참조자를 이용한 Call-by-reference의 단점

 참조자는 함수 정의에 활용했을 때 포인터보다 더 이용하기 쉽다는 장점을 가지고 있다. 그러나 단점 또한 가지고 있는데 이와 관련해 다음 코드를 살펴보자.

int num = 20;
MyFunc(num);
cout<<num<<endl;

 c언어에서는 다음 코드의 결과로 무조건 20이 출력된다. 하지만 c++에서는 함수를 정의할 때 참조자를 이용해 값을 변경할 수 있기 때문에 결과를 확신할 수 없다. 

 

 이런 단점을 극복하기 위해서는 const 참조자를 이용할 수 있다. 이와 관련해 다음 코드를 살펴보자.

void MyFunc(const int &ref) { . . . . }

 위 코드는 함수 내부에서 참조자 ref를 이용하여 값을 변경하지 않겠다라는 의미를 가진다. 따라서, 함수 내부에서 값의 변경이 일어나지 않으면 함수를 선언할 때 ref 앞에 const를 붙여서 선언하는 원칙을 지켜야 한다. 그렇지 않으면 값의 변경 유무를 확인하기 위해서 참조자를 이용하여 선언한 모든 함수의 내부를 일일이 확인해야 하는 불상사가 일어날 것이다. 이는 함수의 코드가 짧을 때는 상관이 없지만 함수의 코드가 길어지면 문제가 생긴다.

 

반환형이 참조형(Reference Type)인 경우

int& RefRetFuncOne(int &ref)
{
    ref++;
    return ref;
}

 위와 같이 반환형이 참조형으로 선언되는 경우가 있다. 

 

 어떻게 이용되는지는 다음 예제를 통해 살펴보자.

#include <iostream>
using namespace std;

int& RefRetFuncOne(int &ref)
{
    ref++;
    return ref;
}

int main(void)
{
    int num1=1;
    int &num2=RefRetFuncOne(num1);

    num1++;
    num2++;
    cout<<"num1 : "<<num1<<endl;
    cout<<"num2 : "<<num2<<endl;

    return 0;
}
num1 : 4
num2 : 4

 위 예제에서 num2는 num1의 참조자로 정의되었다. num1과 num2는 같은 저장 공간에 있는 값을 공유하기 때문에 호출할 때 1이 더해지고 num1++, num2++의 영향을 모두 받아 값이 4로 출력되었다.

+ ref는 지역변수이기 때문에 num1, num2와 같은 저장 공간에 위치하지 않고 함수를 빠져나올 때 소멸된다. 

 

 그렇다면 함수의 반환값을 num2에 저장할 때 참조자 형태가 아닌 기본 자료형으로 저장하면 어떻게 될까? 이와 관련해 다음 예제를 살펴보자.

#include <iostream>
using namespace std;

int& RefRetFuncOne(int &ref)
{
    ref++;
    return ref;
}


int main(void)
{
    int num1=1;
    int num2=RefRetFuncOne(num1);

    num1+=1;
    num2+=10;
    cout<<"num1: "<<num1<<endl;
    cout<<"num2: "<<num2<<endl;

    return 0;
}
num1: 3
num2: 12

 물론 RefRetFuncOne의 반환형은 참조자의 형태 그대로이지만 반환 값을 저장할 때 참조자의 형태가 아닌 일반 변수의 형태로 저장하면 num2는 num1의 참조자가 아닌 아예 별개의 변수가 된다. 따라서 각 변수에 취한 변화는 서로에게 영향을 미치지 못한다.

 

 이렇듯 반환형을 참조자로 정한 함수에서는 반환값을 어떻게 저장하냐에 따라서 형태가 달라지기 때문에 적절하게 상황에 따라서 적절하게 선택하면 된다.

 

 마지막으로 반환값은 참조자이지만 반환형은 기본자료형인 경우를 살펴보자.

#include <iostream>
using namespace std;

int RefRetFuncOne(int &ref)
{
    ref++;
    return ref;
}


int main(void)
{
    int num1=1;
    int num2=RefRetFuncOne(num1);

    num1+=1;
    num2+=10;
    cout<<"num1: "<<num1<<endl;
    cout<<"num2: "<<num2<<endl;

    return 0;
}
num1: 3
num2: 12

 결과는 두 번째 경우와 같다. 그렇다면 무슨 차이가 있는 걸까? 

 

 참조형을 반환형으로 하는 함수의 경우에는 반환값을 참조형, 일반자료형 두 가지 형태로 저장할 수 있는 선택권이 있다. 그러나, 반환형이 일반자료형인 경우에는 반환값이 상수에 불과하기 때문에 참조형의 형태로 저장할 수 없다는 차이점이 있다. 

 

더보기

잘못된 참조의 반환

 다음 예제에서 문제점을 찾아보자.

int& RetuRefFunc(int n)
{
    int num =20;
    num+=n;
    return num;
}

int main(void)
{
    int &ref=RetuRefFunc(10);

    return 0;
}

 위 예제를 통해 ref가 num의 참조자로 선언됐다는 것을 알 수 있다. 그러나 num은 함수를 빠져나오면서 사라지는 지역변수에 불과하다. 따라서 ref가 참조하는 num는 사라지지만 참조자인 ref는 남아있는 아이러니한 상황이 발생한다. 이 경우에는 컴파일도(경고 메세지는 뜨지만) 그냥 완료되기 때문에 조심해야 한다.

 

Const 참조자의 또 다른 특징

상수화된 변수의 참조자 선언

 상수화 선언된 변수에는 일반적인 방법으로 참조자를 선언할 수 없다. 또한, c++에서는 상수화된 변수는 어떠한 경로(참조자 포함)를 통해서도 값의 변경을 허용하지 않는다.

 

 따라서 상수화 변수의 참조자 선언은 다음과 같이 할 수 있다.

const int num = 20; 
const int &ref = num; // 상수화된 변수 num의 참조자 선언

 

리터럴 상수(literal constant)의 const 참조

 리터럴(literal), 혹은 리터럴 상수(literal constant)는 10, 20 같이 프로그램상 표현되는 숫자를 의미한다. 이러한 리터럴 상수는 다음 행으로 넘어가면 메모리 상에서 소멸하는 특징을 가지고 있다. 그렇다면 메모리 상에서 소멸되는 이러한 리터럴 상수를 참조한다는 것은 지금까지 학습한 내용에 따르면 불가능하다. 참조는 메모리 공간에 참조의 대상이 되는 변수가 남아있을 때 가능하기 때문이다.

 

 하지만 c++에서는 다음과 같이 리터럴 상수는 const 참조를 이용해 참조가 가능하다.

const int &ref = 3;

  c++에서는 위의 문장이 성립할 수 있도록 const 참조자를 이용하여 상수를 참조할 때 '임시변수'라는 것을 만든다. 그리고 이 저장 공간에 상수 3을 저장한 이후에 참조자가 이를 참조할 수 있게 한다. 그렇다면 결론적으로 참조자는 상수화된 변수를 참조하게 된다.

 

 그렇다면 c++에서는 왜 이런 애매한 개념을 가능하게 했을까? 이는 다음 예제를 통해서 이해할 수 있다.

#include <iostream>
using namespace std;

int Adder(const int &ref1, const int &ref2)
{
    return ref1+ref2;
}

int main(void)
{
    cout<<Adder(4,3)<<endl;

    return 0;
}
7

 Adder라는 함수에 인자의 전달을 위해서 변수를 새로 선언한다는 것은 번거로운 일이다. 때문에 c++에서는 리터럴 상수의 const 참조를 허용함으로써 이런 번거로움을 해결하고 간단한 함수의 호출을 가능하게 만들었다.

 


  • 출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12