포준 라이브러리의 swap를 봐보자
namespace std
{
template<typename T> //std::swap의 전형적인 구현
void swap(T& a,T& b) //a의 값과 b의 값을 맞바꾼다.
{
T temp(a);
a = b;
b = temp;
}
}
복사가 3번이나 일어나는 딱히 특별할게 없는 코드이다. 그런데 만약 swap를 하려는 객체가 어마무시하게 크다면 어떻게될까? 컴퓨터는 갑자기 쓸때없이 엄청난 연산을 할것이다. 그러기 때문에 연산을 최대한 줄이기 위해 pImp(pointer to implementation)이란 뜻을 가진 설계를 차용해볼것이다.
class WidgetImp1
{
private:
int a, b, c; //아마도 많은 데이터가 있다
vector<double>v; //어쩃든 복사 비용이 높다.
public:
//Widget의 셀제 데이터를 나타내는 클래스
//세부사항은 별로 중요치않음
WidgetImp1(int a, int b, int c) :a(a),b(b),c(c){}
void PrintData()
{
cout << "a : " << a << endl
<< "b : " << b << endl
<< "c : " << c << endl << endl << endl;
cout << "this <---- Address : " << this << endl << endl;
}
};
class Widget //pimp1 관용구를 사용한 클래스
{
private:
WidgetImp1* pimp1; //Widget의 셀제데이터를 가진 객체에 대한 포인터
public:
Widget() {
pimp1 = new WidgetImp1(1,2,3);
}
Widget(const Widget& rhs) { pimp1 = rhs.pimp1; } //Widget을 복사하기 위해, 자신의
//WidgetImp1객체를 복사한다.
Widget& operator = (const Widget& rhs)
{
pimp1 = rhs.pimp1;
return *this;
}
void Print() { pimp1->PrintData(); }
};
int _tmain()
{
Widget original;
cout << "============Original================" << endl << endl;
cout << "original : " << endl; original.Print(); cout << endl << endl << endl;
cout << "============Copy================" << endl << endl;
Widget copy(original);
cout << "Copy : " << endl; copy.Print(); cout << endl << endl << endl;
}
하지만 표준 swap가 이것을 알리가 없다. 그래서 std::swap를 특수화 하여 내부의 pImp1 포인터만 맞바꾸라고 할것이다.
namespace std
{
template< > //이 코드는 T가 Widget일 경우에
void swap<Widget>(Widget& a, Widget& b) //대해 std::swap을 특수화 한것이다.
{ //아직 컴파일되지 않았다.
swap(a.pimp1,b.pimp1)
}
}
코드를 보면 template< >가 보일것이다. 이것은 std::swap를 완전 특수화 하겠다는 것이다. 원래는 namespace std의 구성요소는 마음대로 바꾸지 못하지만 특수화를 하는 것은 가능하다. 위의 코드는 <Widget>을 사용하면 우리가 구현한 swap가 실행이 되게끔 했다. 하지만 위의 코드는 Widget클래스의 Private에 있는 변수를 를 직접 접근하려 하였으므로 정상적으로 실행이 되지 않을것이다. 이를 해결하기 위해 우리는 Widget의 public에 swap함수를 만들면된다.
class PimpWidget
{
private:
int a, b, c;
public:
};
class Widget
{
private:
PimpWidget *pimp1;
public:
Widget()
:pimp1(nullptr) {}
void swap(Widget& other)
{
using std::swap; //이 선언문이 필요한 이유는
//이후의 설명에서 확인할 수 있다.
swap(pimp1, other.pimp1); //Widget을 맞바꾸기 위해, 각 Widget의
} //pimp1 포인터를 맞바꾼다.
};
namespace std
{
template< > //std::swap 템플릿의
void swap<Widget>(Widget& a, Widget& b) //특수화 함수를 살짝 고친 결과
{
a.swap(b); //Widget을 맞바꾸기 위해
} //swap 멤버 함수를 호출한다.
}
이렇게 하면 컴파일도 잘 되고 바람직한 실행도 된다.
그런데 우리가 템플릿으로 구현을 하고 싶으면 어떻게 할까? 두가지 방법이 있다.
1. std::swap오버로딩
namespace std
{
template<typename T> //std::swap을 오버러드한 함수("swap"뒤에
void swap(Widget<T>& a,//"<...>"가 없는 것을 놓치지 말자
Widget<T>& b) //돌아는간다 근데 나쁜예 이다
{a.SWAP(b);}
}
이 방법은 std::swap를 오버로딩 하는 방법이다. 하지만 이건 매우 좋지 않은 방법이다. c++표준 위원회에서 std라는 성역을 건드리는것은 용서하지않는다고한다더라. 하지만 그렇다고 컴파일이 안되는것은 아니다. std영역을 침법하더라도 일단 컴파일까지는 거의 다 되고 실행도 된다. 그런데 실행되는 결과가 "미정의 사항"이라는것이다. 그러니 std::swap에 특수화는 하여도 추가나 오버로드는 하지말자!
2. 새로운 네임 스페이스를 만들어 swap만 집어넣거나 Widget관련된것과 swap을 같이 집어넣기
template<typename T>
class PimpWidget
{
private:
T a, b, c;
public:
PimpWidget(int a, int b, int c)
:a(a),
b(b),
c(c) {}
void print()
{
cout << a << endl << b << endl << c << endl;
};
};
template<typename T>
class Widget
{
private:
PimpWidget<T>* pimp1;
public:
Widget(int a, int b, int c)
:pimp1(nullptr) {
pimp1 = new PimpWidget<int>(a, b, c);
}
void swap(Widget& other)
{
using std::swap; //이 선언문이 필요한 이유는
//이후의 설명에서 확인할 수 있다.
swap(pimp1, other.pimp1); //Widget을 맞바꾸기 위해, 각 Widget의
} //pimp1 포인터를 맞바꾼다.
void print() { pimp1->print(); }
};
namespace WidgetStuff
{
template<typename T> //std::swap 템플릿의
void swap(Widget<T>& a, Widget<T>& b) //특수화 함수를 살짝 고친 결과
{
a.swap(b); //Widget을 맞바꾸기 위해
//swap 멤버 함수를 호출한다.
}
}
int _tmain()
{
Widget<int> a(1,3,5);
a.print();
Widget<int> b(7, 8, 9);
b.print();
using WidgetStuff::swap; //namespace WidgetStuff
//안에 Widget이 들어있으면 swap이 자동으로
//WidgetStuff의 swap으로 바뀌니 이게 없어도됨
swap(a, b);
cout << endl << endl << endl;
b.print();
}
이때 using std::swap나 using WidgetStuff::swap을 선언한 후 swap() 함수를 사용하는것이 아닌
한정자로 swap함수를 호출(std::swap(), WidgetStuff::swap())하면 안된다. 만약 해당 네임스페이스에 swap이라는 함수가 없으면 "이름탐색규칙" 이딴거 없이 swap함수를 찾는걸 포기해버린다.
================================================================
이제부터 정리에 들어가보자. 책에 있는 내용 그대로 적을게(중요한거같아서)
표준 swap,멤버 swap, 비멤버 swap, 특수화한 std:;swap그리고 swap호출 시의 상황들에 대해 집중적으로 조명해보았다. 이제 차근히 정리해보자
첫째, 표준에서 제공하는 swap이 여러분의 클래스 및 클래스 템플릿에 대해 납득할 만한 효율을 보이면, 그냥 아무것도 하지 말고 지내자. 여러분이 마든 타입으로 만든 객체에 대해 'swap'을 시도하는 사용자 코드는 표준 swap을 호출하게 될 것이다. 그리고 아무 문제도 없을것이다.
둘째, 그런데 표준 swap의 효율이 기대한 만큼 충분하지 않다면(여러분의 클래스 혹은 클래스 템플릿이 pimp1 관용구와 비슷하게 만들어져 있을 경우가 십중팔구이다), 다음과 같이 하자
1. 여러분의 타입으로 만들어진 두 객체의 값을 빛나게 빨리 맞바꾸는 함수를 swap이라는 이름으로 만들고, 이것을 public 멤버 함수로 두자, 단, 좀 있다가 이유를 말하겠지만, 이 함수는 절대로 예외를 던져선 안된다.(나중에 항목 29에서 봐보도록 하자 ^^)
2. 여러분의 클래스 혹은 템플릿이 들어 있는 네임스페이스와 같은 네임스페이스에 비멤버 swap을 만드어 넣는다. 그리고 1번에서 만든 swap멤버 함수를 이 비멤버 함수가 호출하도록 만든다.
3. 새로운 클래스(클래스 템플릿이 아니라)를 만들고 있다면, 그 클래스에 대한 std::swap의 특수화 버전을 만들어 넣는다. 그리고 1번에서 만든 swap멤버 함수를 이 멤버 함수가 호출하도록 만든다.
셋째, 사용자 입장에서 swap을 호출할 때 swap을 호출하는 함수가 std::swap을 볼 수 있도록 using 선언을 반드시 포함시키자 그 다음 swap을 호출하되, 네임스페이스 한정자로 붙이지 않도록하자
이것만은 잊지말자
1.std:;swap이 여러분의 타입에 대해 느리게 동작할 여지가 있다면 swap 멤버 함수를 제공하자. 이 멤버 swap은 예외를 던지지 않도록 만들자
2. 멤버 swap을 제공했으면, 이 멤버를 호출하는 비멤버 swap도 제공한다. 클래스(템플릿이 아닌)에 대해서는, std::swap도 특수화 해 두자
3. 사용자 입장에서 swap을 호출할 때는, std:;swap에 대한 using 선언을 넣어 준 후에 네임스페이스 한정 없이 swap을 호출하자
4. 사용자 정의 타입에 대한 std 템플릿을 완전 특수화하는 것은 가능하다. 그러나 std에 어떤것이라도 새로 "추가"하려고 들지말자 "미정의 사항"이된다.
이번꺼 엄청기네 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ