1. std의 표준 swap
namespace std {
template<typename T>
void swap(T& a, T& b)
{
T temp(a); a = b; b = temp; //3번의 복사
}
}
*풀이
- 표준 swap은 타입에 상관없이 1번 호출에 3번 복사가 일어난다.
2-1. 비효율적인 swap동작 예시 ( Class Type )
class WidgetImple {
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; //Widget의 실제 데이터를 가진 객체에 대한 포인터
};
*풀이
- 만약 *pImpl 포인터가 주성분인, 클래스(ex. "Widget") 복사시, 단순히 포인터만 바꿔주면 되는데 클래스 전체가 복사가 되버리고 만다. --> 비효율적
2-2. 해결방안(완전특수화)
(1) swap을 완전 특수화를 해보자. (에러버전)
namespace std{
template <>
void swap<Widget>(Widget& a, Widget& b) {
swap(a.pImpl, b.pImpl); // private 접근 불가하므로 에러
}
}
* 풀이
- 완전특수화가 적용되었다. 하지만 private 멤버인 포인터를 접근못하므로 에러발생한다. -> 아래에서 재 수정해보자.
- 일반적으로 std 네임스페이스의 구성요소는 함부로 변경불가능하지만, 표준 템플릿을 완전 특수화 하는 것은 가능하다.
* 결론
- 위에 얘기한것처럼 포인터에 직접 접근 불가능하므로 Widget의 멤버함수 swap을 만들자.
- 그리고 swap 함수내에서 "swap"을 실시하자.
(2) 완전특수화를 재적용 (정상버전)
class Widget{
public :
…
void swap(Widget& other) // 아래 완전특수화된 std::swap에서 멤버함수를 호출하기전 미리 선언해두자.
{
using std::swap;
swap(pImpl, other.pImpl);
}
…
};
namespace std{
template <>
void swap<Widget>(widget& a, Widget& b) {
a.swap(b); // widget의 멤버함수를 호출한다. 그대신 멤버함수의 swap함수를 신규로 미리 선언해둬야한다. 위 코드와 같이
}
}
* 풀이
- 정상적으로 작동한다.
!! 지금까지 클래스 타입에 대한 효율적인 swap을 알아보았다. 이제 클래스 템플릿타입에서 효율적인 swap 구성에 대해 알아보자.
3. 클래스 템플릿 타입의 특수화(부분 특수화)
namespace std{
template<typename T>
void swap<Widget<T> > (Widget<T>& a, Widget<T>& b) //ERROR!!!
{ a.swap(b) ;}
}
* 풀이
- 클래스템플릿에서는 부분특수화가 가능하나, 함수템플릿은 불가능하다.
- 부분특수화는 template 다음 꺽쇠에는 변경하질 않을 타입, 그다음 꺽쇠에는 변경될 타입을 명시하면된다.
- 특수화가안된다. --> 어떻게 해결할까? --> 오버로딩? 안된다. std에는 아무것도 변경되어선 안된다. 특수화제외하곤
* 해결방법
- 비멤버 swap 을 선언하고 이 안에서 멤버함수 swap을 호출하자. (멤버함수 swap 안에는 std::swap 을쓰면되겠지? 이경우에서는..)
- 단, 비멤버 swap은 std네임스페이스안에 있으면 안된다.
namespace WidgetStuff {
…
template<typename T>
class Widget {…{;
…
template<typename T>
void swap(Widget& a, Widget<T>& b) { a.swap(b); } //비멤버 swap이며, std 네임스페이스에 포함되면안됨
}
* 결론
- 클래스타입이나 템플릿클래스타입이나, 비멤버함수에서 멤버함수를 호출한다.
- 단, 클래스타입은 swap의 완전특수화를 미리 준비해두자.
4. swap 호출 순서제어
1. T타입 전용 swap (동일 네임스페이스안에있는 swap부터 찾아낸다)
2. std::swap의 특수화버전을 호출한다.
3. std::swap 표준버전을 호출한다.
위와 같이 순서를 제어하려면 1번과 3번의코드를 미리 선언해두고 다음과 같이 코드를 작성해야한다.
template<typename T>
void doSomething(T& obj1, T& obj2)
{
using std::swap;
…
swap(obj1, obj2);
…
}
5. 정리
(1) 표준 swap이 납득할만한 효율을 보이면 그냥 표준 swap쓰자.
(2) 납득할만한 효율이 아니면 다음과같이 해결하자
1) 멤버함수로 효율적인 swap을 구성하자
2) 해당 클래스 또는 템플릿이 들어있는 네임스페이스와 같은 유효범위에 비멤버 swap을 만들어두자. 그리고 이 비멤버함수가 (1)을 호출하도록 하자
3) 클래스(클래스 템플릿이 아니라)를 만들려한다면 std::swap의 특수화버전 미리준비하자..
(3) using 붙여서 swap을 호출하도록 하자.(ex. using std::swap;)
6. 유의점
- 멤버 swap은 절대 예외를 던지지말도록하자 -> 항목 29에서 확인해보자.
* 요점정리
(1) 표준 swap이 느리면, swap 멤버함수를 제공하자.. 예외는 못던지게만들구
(2) 멤버 swap을 만들었으면, 이멤버 함수를 호출하는 비멤버 swap도 만들자 ( 클래스 타입이면 std::swap의 완전특수화도)
(3) std::swap에 대한 using 선언을 넣어준후 네임스페이스 한정 없이 swap 호출
(4) std 템플릿 완전특수화는 가능, 하지만 std를 변경하려고는 하지마라
(이번 항목 정리는 템플릿에 대해 공부하느라 꽤 오래걸렸다.ㅜㅜ)
참고 사이트
'책 정리 > Effective C++ 3rd' 카테고리의 다른 글
항목 27. 캐스팅은 절약, 또 절약! 잊지 말자 (0) | 2020.02.03 |
---|---|
항목 26. 변수 정의는 늦출 수 잇는 데까지 늦추는 근성을 발휘하자 (0) | 2020.01.15 |
항목 24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자 (0) | 2020.01.10 |
항목 23. 멤버함수 보다는 비멤버 비프렌드 함수와 더 가까워지자 (0) | 2020.01.09 |
항목 22. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자 (0) | 2020.01.09 |
댓글