스마트 포인터에서 포인터와 같은 암시적 형 변환을 지원하게 해주기 위해 코드를 작성해보면 그 작업이 엄청 까다롭다는 것을 깨달을 수 있다.
template<typename T>
class SmartPtr
{
public:
explicit SmartPtr(T* realPtr);
};
class Top
{};
class Middle : public Top
{};
class Bottom : public Middle
{};
SmartPtr<Top> pt1 = SmartPtr<Middle>(new Middle);
SmartPtr<Top> pt1 = SmartPtr<Bottom>(new Bottom);
포인터의 경우 암시적 변환이 가능하지만 SmartPtr<Top>과 SmartPtr<Middle>은 컴파일러가 봤을 때 완전 별개의 클래스입니다. 따라서 직접 코드를 작성해줘야 합니다.
template<typename T>
class SmartPtr
{
public:
explicit SmartPtr(T* realPtr);
SmartPtr(SmartPtr<Middle>& other);
SmartPtr(SmartPtr<Bottom>& other);
...
}
위와 같이 모든 생성자를 직접 만들어 줘야하는데 원칙적으로는 우리가 원하는 생성자의 개수는 '무제한'입니다. 무제한 적인 코드를 직접 모두 작성해주기란 불가능합니다. 따라서 생성자를 만들어내는 템플릿을 사용해야합니다. 아래와 같이 멤버 함수 템플릿을 작성하면 됩니다.
template<typename T>
class SmartPtr
{
public:
template<typename U>
SmartPtr(SmartPtr<U>& other); //일반화된 복사생성자를 만들기 위한 멤버 템플릿
...
}
위와 같이 다른 템플릿을 써서 인스턴스화되지만 타입이 다른 객체로부터 원하는 객체를 만들어 주는 생성자를 가리켜 일반화 복사 생성자(generalized copy constructor)라고 합니다.
위의 코드에는 문제가 있는데 SmartPtr<Top>으로 부터 SmartPtr<Bottom>을 만든다거나 SmartPtr<int>로 부터 SmartPtr<double>를 만들어 버릴 수도 있습니다.
auto_ptr 및 tr1::shared_ptr에서 쓰는 방법을 그대로 따라서 위와 같은 문제를 막기 위한 제약을 걸 수 있습니다.
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;
}
heldPtr(other.get()) 이 부분 때문에 U타입 포인터에서 T타입 포인터로 암시적 변환이 불가능하다면 컴파일 에러가 발생하게 됩니다.
멤버 함수 템플릿은 생성자 뿐 아니라 대입 연산 등에도 쓰일 수 있다.
template <typename T>
class SmartPtr
{
public:
template<typename U>
SmartPtr& operator=(SmartPtr<U>& other);
}
멤버 함수 템플릿 사용에 있어서 사용자들이 착각 할 수도 있는 부분이 있는데 이 멤버 템플릿은 언어의 규칙을 바꾸지는 않습니다.
예를 들면 클래스에 생성자나 복사 생성자가 선언되어 있지 않으면 컴파일러가 자동으로 만들어 주는데 일반화 복사 생성자가 있다고 해서 기본 복사 생성자가 안만들어지는 것은 아니라는 말입니다.
'이론 > [책]Effective C++' 카테고리의 다른 글
[항목55]Boo자유친! 부스트를 늘 여러분 가까이에 (0) | 2018.01.02 |
---|---|
[항목44]매개변수에 독립적인 코드는 템플릿으로부터 분리시키자 (0) | 2017.12.26 |
[항목43]템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자 (0) | 2017.12.25 |