본문 바로가기
책 정리/Effective C++ 3rd

항목 45. "호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방!

by ocean20 2020. 3. 24.

1. 스마트포인터 vs 보통의 포인터

  1) 스마트포인터 일반포인터처럼 동작하나, 추가적인 기능 덤으로 갖고 있다(ex. auto_ptr, tr1::shared_ptr, list::iterator 등이 있다.) 하지만 보통의 포인터 스마트 포인터로 대신할 없는 특징이 있다. 그중 하나가 암시적 변환 지원한다는 점이다.

 

  2) 보통의 포인터에서 파생클래스는 기본클래스로 변환이 가능하며, 비상수객체에서 상수객체로서의 암시적 변환이 가능하다는 것이다. 스마트포인터 암시적변환기능을 지원하지 않는다. 왜냐하면 컴파일러가 스마트 포인터간에 연관성을 찾지 못하기 때문이다.

 

하지만, 스마트포인터는 "멤버 함수 템플릿" 사용하여 암시적 변환 기능을 얻어 있다.

 

2. 멤버 함수 템플릿

template<typename T>
class SmartPtr{
public :
  template<typename U>   // "일반화된 복사 생성자"를 만들기 위해 마련한 멤버 템플릿
  SmartPtr<const SmartPtr<U>& other);
  …
};

* 풀이

  1) 해당 멤버 함수 템플릿은 SmartPtr<T> SmartPtr<U>로부터 생성될수 있다는 얘기이다.(복사생성자이므로) 이를 "일반화 복사 생성자" 라 부른다.

 

  2) 여기서 explicit 키워드가 빠진 이유를 알아보자. 기본 포인터 방식은 암시적 변환이 가능하다. 따라서 스마트 포인터도 이와 동일은 개념의 기능을 갖춰야 한다. 따라서 explicit 명시적 변환 키워드를 서는 안된다.

 

  * 위 코드 문제점 : 스마트 포인터는 결국 기본포인터의 기능을 갖춰야 하는데  코드는 SmartPtr<double>로부터 SmartPtr<int> 변환이 되버린다. 기본 C++ 기본 규칙을 어지럽힌다. 이를 바로잡기 위해서 변경된 다음 코드를 살펴보자

template<typename T>
class SmartPtr{
public :
  template<typename U>
  SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get()) { … }

  T* get() const { return heldPtr; }
  …
private :
  T *heldPtr;   // SmartPtr에 담긴 기본제공 포인터
};

*풀이 : SmartPtr<T> 데이터 멤버인 T* 타입의 포인터를 SmartPtr<U> 들어 있는 U* 타입의 포인터로초기화하여 문제해결하였다.

 

3. 복사생성자 규칙

  - 멤버함수 템플릿은 코드 재사용만큼이나 훌륭한 기능이지만, C++언어의 기본 규칙까지는 바꾸진 않는다.

만약 tr1::shared_ptr객체가 자신과 동일한 타입의 다른 tr1::shared_ptr객체로부터 생성되는 상황이라면, 컴파일러는 tr1::shared_ptr 복사 생성자를 만들까? 아니면, 일반화 복사생성자를 만들까?

 

  - 동일 타입의 일반화 복사생성자를 생성하지 않았다면, 보통의 복사생성자가 만들어질 것이다. 따라서 어떤 클래스의 복사생성을 전부 여러분의 손아귀에 넣고 싶다면, "보통의" 복사 생성자까지 직접 선언해야 한다.


이것만은 잊지 말자!

1. 호환되는 모든 타입을 받아들이는 멤버함수를 만들려면 멤버 함수 템플릿을 사용합시다.

2. 일반화된 복사 생성 연산과 일반화된 대입연산을 위해 멤버 템플릿을 선어했다 하더라도, 보통의 복사생성자와 복사 대입 연산자는 여전히 직접 선언해야 한다.

 

 

템플릿 항목은 생소한부분이라 이해하는데 오래걸린다.. 아직도 100% 이해하진 못했다. 곱씹으면서 되새겨봐야겠다.

이제 항목10개정도 남았는데, 언제쯤 끝낼지..3월을 목표로 하고있다.

댓글