C++: 포인터 vs. 참조자

C++ 프로그래밍 언어에서는 객체를 간접적으로 접근하게 해주는 수단으로 크게 2가지를 제공해준다. 한가지가 포인터; pointer고 나머지 하나가 – C 언어에는 없는 – 참조자; reference 다.

사실 이 둘은 거의 비슷한 용도로 쓰인다.

  • 크기가 커서 직접 전달하는게 힘든 함수 인자 혹은 멤버 변수 전달용
  • 값에 의한 전달; pass by value 로 할 수 없는 일의 처리 (예: swap 함수)
  • 다형성; polymorphism 이 사용되어야 하는 상황

C++ faq-lite 에서 설파하고 있듯이 – 혹은 스캇 마이어스; Scott Meyers의 More Effective C++1 에서 설명하고 있듯이,

사용할 수 있다면 참조자를, 어쩔 수 없다면 포인터를 써라;

Use references when you can, and pointers when you have to

즉 가능하면 참조자를 사용하는 것이 좋다. 언제가 _어쩔 수 없는 경우_에 해당할까? 바로 대상이 NULL일 수 있는 경우만이다. 이런 경우에는 어쩔 수 없이 포인터를 쓴다.

우선 참조자를 써야하는 대표적인 사례 는 이런 것 같다(위에서 언급한 첫번째 케이스). 매우 거대한 객체를 유지하고 있는 클래스가 있는데, 이 거대한 객체를 “외부에서 접근하게 해주려면” 무엇을 써야할까?

우선 포인터를 사용하면 다음과 같은 코드가 될 것이다.

SomeHeavyObject* somClass::GetHeavy() { return m_HeavyObjectPointer; }

반면에 참조자를 사용하면 다음과 같은 코드가 될 것이다.2

SomeHeavyObject& someClass::GetHeavy() { return *m_HeavyObjectPointer; }

둘의 차이는 무엇일까? 가장 중요한 차이는 저 반환값을 가지고 NULL 검사를 해야하는지 여부일 것이다. 즉, 앞에서 설명한 원칙에 따라 포인터를 반환한다는 것은 “해당하는 객체가 없을 수; NULL일 수 있다” 라는 의미를 부여하게 된다. 그래서 저 멤버 함수를 호출할 사람은 당연히 NULL검사를 해야한다. (왜냐하면 NULL이 아닌게 보장이 되면 당연히 참조자를 썼을 것이기 때문에)

ps. 근데 왜 내가 지금 보고있는 코드는 거대 멤버 객체를 반환하는데 포인터인걸까 Orz (위의 경우와는 더더욱 다르게 내부적으로도 그냥 객체가 바로 저장되도록 설계되어 있다; 그래서 절대 NULL일 수가 없다).


  1. C++ 프로그래머라면 꼭 읽어야하는 Effective C++ 씨리즈의 두번째 책이다. 이 책의 항목에서 이 글의 주제를 상세히 다루고 있으니 읽어보자. ↩︎

  2. 여기에서는 m_HeavyObjectPointerNULL이 아니라는 보장이 내부적으로 되어 있어야 한다. 그렇지 않다면 NULL 참조자라는 큰 문제가 생긴다. (보통 저런 코드에는 assert(m_HeavyObjectPointer); 같은 코드를 넣어서 미리 검사를 하는 경우가 많다) ↩︎