본문 바로가기

컴퓨터과학

[Effective C++] (6-2) auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용합니다

Effective C++

* 대리자 클래스

다른 어떤 형식의 행동을 흉내 내고 보강하는 것이 존재 이유인 크래스의 예입니다. 대리자 클래스는 다양한 목적으로 도입되는데, std::vector<bool>::reference의 목적은 std::vector<bool>의 operator[]가 마치 비트에 대한 참조를 돌려주는 듯한 환상을 제공하는 것입니다. 그리고 표준 라이브러리의 똑똑한 포인터 형식들(Chapter(4) 참고)은 생 포인터에 자원 관리 기능을 접목하는 대리자 클래스들입니다. 대리자 클래스의 유용함은 널리 인정되고 있습니다. 사실 '대리자(Proxy)' 설계 패턴은 소프트웨어 설계 패턴 만신전을 아주 오래전부터 차지하고 있는 패턴 중 하나입니다. 

대리자 클래스 중에는 클라이언트에게 명백히 드러나도록 설계된 것들이 있습니다. std::shared_ptr와 std::unique_ptr가 좋은 예입니다. std::vector<bool>::reference가 그러한 '보이지 않는' 대리자의 예이며, 그것의 std::bitset 버전이라 할 수 있는 std::bitset::reference 역시 그러한 대리자의 예입니다. 

표준 라이브러리 외에, 대리자 클래스는 표현식 템플릿(expression template)이라는 기법을 사용하는 C++ 라이브러리들에서도 흔히 쓰입니다. 그런 라이브러리들은 원래 수치 처리 코드의 효율성을 개선하기 위해 개발된 것입니다. 예를 들어 Matrix라는 클래스와 Matrix의 객체 m1, m2, m3, m4를 이용해서 다음과 같은 코드를 작성했다고 합시다. 

 

<의사 코드>

Matrix sum = m1 + m2 + m3 + m4;

 

우변의 표현식을 훨씬 더 효율적으로 계산하는 한 가지 방법은, Matrix 객체의 operator+를 연산 결과 자자체가 아니라 연산 결과에 대한 대리자를 돌려주도록 구현하는 것입니다. 즉, 두 Matrix 객체에 대한 operator+가 하나의 Matrix 객체를 돌려주는 것이 아니라 이를테면 Sum<Matrix, Matrix> 같은 대리자 클래스의 객체를 돌려주게 합니다. std::vector<bool>::reference와 bool의 경우처럼, 그러한 대리자 클래스는 Matrix로의 암묵적 변환을 지원할 것이며, 그 덕분에 "=" 우변의 표현식에 의해 산출된 대리자 객체를 이용해서 sum을 초기화하는 것이 가능해집니다. (전통적으로, 그러한 대리자 객체의 형식은 초기화 표현식 전체를 부호화합니다. 이를테면 Sum<Sum<Sum<Matrix, Matrix>, Matrix, Matrix> 같은 형식이 되는데, 이런 복잡한 형식은 당연히 클라이언트에게 보여주지 않는 것이 좋습니다.)

대체로, 이러한 "보이지 않는" 대리자 클래스는 auto와 잘 맞지 않습니다. 그런 클래스는 해당 객체의 수명이 한 문장 이상으로 연장되지는 않는다는 가정하에서 설계되는 경우가 많으며, 따라서 그런 형식의 변수를 생성하는 것은 라이브러리 설계의 근본적인 가정들을 위반하는 경향이 있습니다. std::vector<bool>::reference가 바로 그런 경우이며, 앞의 예제에서 보았듯이 그런 가정을 위반하면 미정의 행동이 발생할 수 있습니다.

정리하자면, 다음과 같은 형태의 코드는 피해야 합니다.

 

auto someVar = "보이지 않는" 대리자 클래스 형식의 표현식;

 

그런데 대리자 객체가 쓰이고 있는지를 어떻게 파악해야 할까? 대리자를 사용하는 소프트웨어가 그 사실을 굳이 광고하려 드는 경우는 별로 없습니다. 애초에, 대리자 클래스는 보이지 않는 존재 입니다(적어도 개념 적으로는). 그리고, 대리자 객체의 존재를 확인했다고 해도, 꼭 auto와 Chapter(5)에서 본 수많은 장점을 포기해야 할까요??

우선 대리자 객체의 존재를 확인하는 방법부터 살펴보겠습니다. '보이지 않는' 대리자 클래스는 일상적적인 용법에서는 그 존재가 드러나지 않도록 설계된 것이지만, 그래도 라이브러리의 문서에는 대리자 클래스의 존재가 명시되어 있는 경우가 많습니다. 독자가 사용하는 라이브러리의 기본적인 설계상의 결정들에 익숙해질수록, 그 라이브러리 안에서 쓰이는 대리자들의 존재 때문에 낭패를 볼 가능성이 줄어듭니다.

문서화의 결함을 헤어 파일이 채워주기도 합니다. 소스 코드에서 대리자 객체의 존재를 완전히 숨길 수 있는 경우는 거의 없습니다. 일반적으로 대리자 객체는 클라이언트가 호출하도록 만들어진 어떤 함수가 돌려주며, 그런 함수의 서명을 보면 대리자 객체의 존재를 확인할 수 있는 경우가 많습니다. 예를 들어 다음은 std::vector<bool>::operator[]의 명세입니다. 

 

namespace std // C++ 표준에서 발췌

{

 template <class Allocator>

 class vector<bool, Allocator>

 {

  public:

  ...

   class reference { ... };

   reference operator[](size_type n);

  ...

 };

}

 

 

*** Key Point ***

- "보이지 않는" 대리자 형식 때문에 auto가 초기화 표현식의 형식을 "잘못" 연역할 수 있다.

- 형식 명시 초기치 관용구는 auto가 원하는 형식을 연역하도록 강제한다.