요약
평소에 사용하는 swap함수는 우리가 흔하게 사용하는 형식이다.
예시코드
template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
위와같은 swap() 함수를 사용하면 사본(temp)가 필요없는 경우도 있는데,
사본까지 복사하는 게 비효율적이라고 생각되면,
pimpl(pointer to implementation)이란 기법을 사용한다.
사본을 이용한 swap이 아닌 포인터만 바꾸어 객체가 가리키는 주소만 다르게하는 방법이다.
예시코드
class WidgetImpl{
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:
WidgetImple *pImpl; // Widget의 실제 데이터를 가진 객체에 대한 포인터
}
위 코드에서 실제로 할 일은 포인터만 맞바꾸어주는 것이지만
표준 swap은 복사를 Widget 객체를 3번 복사하고
멤버데이터로 갖고있는 WidgetImple 까지 3번 복사할 것이기때문에 비효율적이니,
템플릿 특수화를 한다.
예시코드
namespace std{
template<>
void swap<Widget>(Widget& a, Widget& b)
{
swap(a.pImpl, b.pImpl);
}
}
template<>는 컴파일러에게 std::swap의 완전 템플릿 특수화 함수라는 것을 알려주는 것이며,
은 T가 Widget일 경우에 대한 특수화라는 사실을 알려주는 것.
일반적으로 std 네임스페이스의 요소는 함부로 변경하거나 할 수 없지만,
프로그래머가 직접 만든 타입에 대해 표준 템플릿을 완전 특수화하는 것은 허용이 된다.
지금은 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()을 실행시켜야한다. but, 위 코드는 컴파일되지 않는다.
이유는 C++는 클래스 템플릿에 대해서는 부분 특수화(partial specialization)를 허용하지만
함수 템플릿에 대해서는 허용하지 않도록 정해져 있다.
해결책
비멤버함수로 swap을 오버라이딩
잊지말자!
표준에서 제공하는 swap이 클래스 및 클래스 템플릿에 대해 납들할 만한 효율을 보이면 아무것도 하지말자.
비효율적이라면 다음과 같이 하자.
효율적인 swap 함수를 멤버함수로 만들자.
클래스 혹은 템플릿이 들어있는 네임스페이와 같은 곳에 비멤버 swap을 만들고 이 함수 에서 1번의 멤버함수를호출하자.
새로운 클래스를 만들고 있다면, 그 클래스에 대한
std::swap을 볼 수 있도록 using 선언을 반드시 포함하자.
느낀점
이번항목은 꽤 어려운부분인게 느껴진다.(진짜 이해하려고 같은 곳을 여러번 봐야했다 ㅋㅋ)
그래서 세세한 이해보다는 이런방법도 있다는 식으로 알아두는게 좋을 듯하다.