swap함수는 우선 일반적으로 이런식으로 구현되어있다.
namespace std {
template<typename T>
void temp(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
}
그래서 우리가 일반적인 std::swap의 기능을 이용하면 복사가 필수적으로 발생하기 때문에 여러모로 손해를 보는 경우가 있다.
class WidgetImpl {
public:
...
private:
int a, b, c;
std::vector<double> v;
};
class Widget {
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs)
{
...
*pImpl = *(rhs.pImpl);
...
}
...
private:
WidgetImpl *pImpl;
};
우선 위와 같이 멤버가 많은 클래스를 보완하기 위해 Impl클래스를 설계하여 본 클래스는 Impl 클래스의 포인터만 가지고 있도록 설계했다. 실제로 swap을 할때는 단순히 pImpl만 교환하면 된다. 그런데 std::swap함수는 이를 알리가 없다. Widget 객체를 복사하면서 자동으로 그 안에 있는 WidgetImpl객체마저도 복사하려 들것이다. 그래서 우리는 std::swap을 Widget에 대해 특수화를 하면 된다. 우선 아이디어만 구상 해보면 아래 코드처럼 될 것이다.
namespace std {
template<>
void swap<Widget>(Widget& a, Widget& b)
{
swap(a.pImpl, b.pImpl); // 각자의 pImpl만 맞바꿈
}
}
자, 하지만 pImpl은 private영역에 있기때문에 접근이 되지 않는다. 그래서 우리는 이것을 멤버함수를 이용하여 해결 할 수 있다.
class Widget {
public:
...
void swap(Widget& other)
{
using std::swap;
swap(pImpl, other.pImpl);
}
...
};
namespace std {
template<>
void swap<Widget>(Widget& a, Widget& b)
{
a.swap(b);
}
}
이런식으로 멤버함수 내에서는 값에 접근이 가능하기 때문에 멤버함수로 선언을 해두고 그 멤버함수를 특수화 함수 내부에서 호출하는 식으로 해결 할 수 있다.
자, 그렇다면 만약에 swap하려는 클래스 자체가 이미 template이면 어떻게 할까?
template<typename T>
class WidgetImpl { ... };
template<typename T>
class Widget{ ... };
namespace std {
template<typename T>
void swap<Widget<T>>(Widget<T>& a, Widget<T>& b)
{
a.swap(b);
}
}
자, 뭔가 이런식으로 해결하면 가능할것 같은데... std 네임스페이스는 사용자가 특수화 선언은 가능하지만 직접 새로운 템플릿을 추가하는 것은 하지 못하도록 막아놓았다. 그렇다면 어떻게 해야할까? 방법은 간단하다. 바로 std 네임스페이스 내부에 선언하지만 않으면 된다.
namespace WidgetStuff {
template<typename T>
class WidgetImpl { ... };
template<typename T>
class Widget{ ... };
template<typename T> // 비멤버 swap 함수
void swap<Widget<T>>(Widget<T>& a, Widget<T>& b)
{
a.swap(b);
}
}
자 이런식으로 설계를 해두면 이제 이런식으로 사용할 수 있다.
template<typename T>
void doSomething(T& a, T& b)
{
...
swap(a, b);
...
}
자 이 함수에 만약 Widget<T>타입의 객체가 들어오면 우리가 아까 선언한 비멤버함수가 잘 호출 되어 줄 것이다. 그런데 여기서 우리는 Widget<T>타입 외에도 여러가지 객체에 대해서 swap을 호출해 주고 싶다면 우리는 다음과 같은 코드를 작성할 수 있다.
template<typename T>
void doSomething(T& a, T& b)
{
using std::swap;
...
swap(a, b);
...
}
이렇게 해주면 매개변수를 보고 이것이 std::swap에 적절한 타입이면 std::swap을 호출해 주고 Widget<T>타입이라면 그에 맞는 함수를 호출 해 줄것이다. 그런데 만약 여기서 std::swap(a, b)로 함수를 호출하면 절대로 Widget<T>타입의 swap을 호출해줄리가 없으므로 조심하도록 하자.
그리고 내가 직접 코드를 작성하면서 알게된 점인데 특수화를 할때는 반드시 헤더 파일에 선언을 하고 cpp파일에 정의를 하는 식으로 나누어서 하도록 하자. 그렇게 안하면 자꾸만 컴파일이 안되고 오류가 난다.
자, 이번 항목은 내용도 길고 이해하기도 정말 어렵고 복잡했었는데 한번 직접 코드로 구현을 해보면 아, 이런거구나 하고 이해하게 된다. swap은 언제가는 한 번쯤 쓰게될 함수이므로 꼭 기억해두도록 하자.
'C++ > Effective C++' 카테고리의 다른 글
항목 24 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자 (0) | 2024.01.22 |
---|---|
항목 23 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자 (0) | 2024.01.21 |
항목 22 데이터 멤버가 선언될 곳은 private 영역임을 명심하자 (1) | 2024.01.21 |
항목 21 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자 (1) | 2024.01.21 |
항목 20 '값에 의한 전달'보다는 '상수객체 참조자에 의한 전달'방식을 택하는 편이 대개 낫다 (2) | 2024.01.20 |