[C++] 포인터(Pointer) vs 레퍼런스(Reference)
개요
C++ 프로그래밍을 시작하면 레퍼런스(Reference : 참조자)라는 새로운 개념을 접하게 됩니다.
언뜻 보면 C언어를 공부할 때 여러분들을 굉장히 괴롭혔던 포인터(Pointer)와 유사해 보입니다.
어떠한 대상을 가리킨다는 점에서는 같습니다.
하지만 포인터와 레퍼런스는 여러가지 차이점이 있습니다.
그 중에서 C++ 프로그래밍을 할 때 반드시 알아야 할 두 가지 중요한 차이점에 대해서 이야기 해보겠습니다.
1. NULL 허용 여부
우선 NULL값을 허용하는 것에 대한 문제입니다.
포인터는 아시다시피 NULL을 허용하지만 레퍼런스는 NULL이 될 수 없습니다. 이 부분이 굉장히 중요한데요.
포인터를 다룰 때 수없이 우리를 마주쳤던, ‘Null pointer exception’ 또는 ‘Segmentation Fault’ 에러들의 대부분의 원인은 포인터를 초기화하지 않거나 NULL을 가리키고 있는 포인터에 접근했을 때 발생합니다.
struct person {
int birthday;
};
struct person *peter = NULL;
peter->birthday = 1220;
위 코드처럼 NULL 포인터를 참조 하고 있는 peter 변수를 접근 할 때 에러가 발생합니다.
따라서 포인터 변수를 사용할 때에는 반드시 아래와 같이 validation 하는 처리를 해줘야 이런 에러들을 방지할 수 있습니다.
if (peter)
peter->birthday = 1220;
else
printf("peter is null\n");
하지만 여기에서 포인터 대신 레퍼런스를 사용하면 이런 문제는 발생하지 않습니다. 레퍼런스는 NULL을 할당할 수 없도록 제한되기 때문입니다. 포인터와 목적은 같지만 잘못된 참조로 인해 발생되는 오류를 방지하기 위해 고안되었다고 이해하시면 됩니다.
이러한 특성은 함수 매개변수(Function parameter)에 사용될 때에도 동일하게 적용이 되는데요.
void isBirthdayByPointer(struct person*);
void isBirthdayByReference(struct person&);
위와 같이 선언된 함수가 있을 때 포인터 매개변수를 갖는 함수는 매개변수에 접근할 때 반드시 NULL 여부를 체크해야 합니다.
반면 레퍼런스 매개변수를 갖는 함수는 NULL을 허용하지 않기 때문에 생략이 가능합니다.
매개변수가 NULL을 허용하는지 여부는 프로그램을 설계할 때 굉장히 중요한 부분입니다. 이 차이로 인해 함수의 설계사상이 완전히 달라질 수 있기 때문입니다. 앞으로 여러분들은 함수를 설계할 때 이러한 차이를 반드시 유념하시기 바랍니다.
2. 참조 대상 할당 및 접근
앞서 살펴본 NULL 허용 여부는 참조 대상을 할당하는 방법에서 오는 차이라고 볼 수도 있는데요.
포인터는 할당 할 때 참조 대상에 대해 & 연산을 통해 주소값을 할당합니다. 반면 레퍼런스에는 참조 대상을 그대로 할당합니다.
int a = 10;
int *p = &a; // 포인터는 주소값을 할당
int &r = a; // 레퍼런스는 대상을 직접 할당
따라서 레퍼런스 변수에는 애초에 NULL을 할당 할 수가 없는 것이죠.
또한 레퍼런스는 선언과 동시에 초기화를 하지 않으면 컴파일 오류가 발생합니다.
이러한 차이는 함수 호출 시에도 같이 적용됩니다.
struct person peter;
isBirthdayByPointer(&peter); // 주소값 입력
isBirthdayByReference(peter); // 직접 입력
이렇게 넘겨 받은 참조를 사용할 때에도 포인터는 *, -> 등의 포인터 연산자를 통해 접근해야 하지만 레퍼런스는 마치 지역변수처럼 접근할 수 있습니다. (다만 레퍼런스의 값을 변경하면 레퍼런스가 참조하고 있는 실제 변수의 값이 변경됩니다)
결론
레퍼런스는 포인터를 잘못 사용해서 생기는 수많은 재앙과도 같은 문제들을 최소화하기 위해 등장했다고 보시면 되겠습니다. 때문에 무한한 가능성을 열어둔 포인터와 달리 레퍼런스는 위에서 살펴본 것과 같이 여러가지 제약 사항이 존재합니다.
하지만 이런 제약사항에도 Call by reference로 활용하는 데에는 전혀 문제가 되지 않기 때문에 보다 안정성이 있는 프로그램을 위해 적극적으로 활용하시길 권해드립니다.
C++ FAQ에서 포인터와 레퍼런스에 대해 아래와 같이 설명하고 있습니다.
Use references when you can, and pointers when you have to
사용할 수 있다면 참조자를, 어쩔 수 없다면 포인터를 써라