2016년 3월 23일 수요일

[Effective C++] 항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자

캐스팅 문법 정리.
1.     스타일의 캐스트.
(T) 표현식 // 표현식 부분을 T 타입으로 캐스팅합니다.
2.     함수 방식 캐스트입니다문법이 함수 호출문 같지요.
T (표현식) // 표현식 부분을 T 타입으로 캐스팅합니다.
어떻게 쓰든 이들이 가진 의미는 똑같습니다단지 괄호를 어디에 썼느냐만 다를 뿐이지요.

C++ 4가지로 이루어진새로운 형태의 캐스트 연산자를 독자적으로 제공합니다
(
신형 스타일의 캐스트 혹은 C++ 스타일의 캐스트라고 부르죠)
           const_cast<T>(표현식)
           dynamic_cast<T>(표현식)
           reinterpret_cast<T>(표현식)
           static_cast<T>(표현식)

l  const_cast
객체의 상수성(constness)을 없애는 용도 혹은 휘발성(volatileness)을 제거하는 용도로 사용됩니다이런 기능을 가진 C++ 스타일의 캐스트는 이것밖에 없습니다.
l  dynamic_cast
이른바 안전한 다운캐스팅(safe downcasting)’을 할 때 사용하는 연산자입니다주어진 객체가 어떤 클래스 상속 계통에 속한 특정 타입인지 아닌지를 결정하는 작업에 쓰입니다구형 스타일의 캐스트 문법으로는 흉내조차도 낼 수 없는 유일한 캐스트이기도 합니다덤으로신경 쓰일 정도로 런타임 비용이 높은 캐스트 연산자로도 유일하고요.
l  reinterpret_cast
포인터를 int로 바꾸는 등의 하부 수준 캐스팅을 위한 만들어진 연산자로서이것의 적용 결과는 구현환경에 의존적입니다 (이식성이 없다는 뜻이죠)이런 캐스트는 하부 수준 코드 외에는 거의 없어야 합니다.
l  static_cast
암시적 변환[비상수 객체를 상수 객체로 바꾸거나(3), int double로 바꾸는 등의 변환]을 강제로 진행할 때 사용합니다.흔히들 이루어지는 타입변환을 거꾸로 수행하는 용도(void*를 일반 타입의 포인터로 바꾸거나기본 클래스의 포인터를 파생 클래스의 포인터로 바꾸는 등)로도 쓰입니다.물론 상수 객체를 비상수 객체로 캐스팅하는데 이것을 쓸 수는 없습니다 (위에서 말한 const_cast 연산자밖에 안 됩니다).

구형 스타일의 캐스트는 요즘도 적법하게 쓰일 수 있지만,그보다는 C++ 스타일의 캐스트를 쓰는 것이 바람직합니다.우선코드를 읽을 때 알아보기 쉽기 때문에소스 코드의 어디에서 C++의 타입 시스템이 망가졌는지 찾아보는 작업이 편해집니다.둘째캐스트를 사용한 목적을 더 좁혀서 지정하기 때문에, 컴파일러 쪽에서 사용 에러를 진단할 수 있습니다. 상수성을 없애려고 한 부분에다가 const_cast 대신에 다른 신형 스타일의 캐스트를 실수로 썼다면 코드 자체가 컴파일되지 않으므로 좋다는 것입니다.

필자가 구형 스타일의 캐스트를 쓰는 경우는 딱 한군데 같아요.객체를 인자로 받는 함수에 객체를 넘기기 위해 명시호출 생성자를 호출하고 싶을 경우인데.

class Widget {
public:
           explicit Widget(int size);
           ...
};

void doSomething(const Widget& w);

doSomething(Widget(15)); // 함수 방식 캐스트 문법으로 int로부터 Widget을 생성합니다.

doSomething(static_cast<Widget>(15)); // C++ 방식 캐스트를 써서 int로부터 Widget을 생성합니다.

캐스팅은 그냥 어떤 타입을 다른 타입으로 처리하라고 컴파일러에게 알려 주는 것밖에 없다고 생각할 수 있는데오해입니다어떻게 쓰더라도(캐스팅으로 명시적으로 바꾸거나컴파일러가 암시적으로 바꾸거나일단 타입 변환이 있으면이로 말미암아 런타임에 실행되는 코드가 만들어지는 경우가 정말 적지 않습니다.


int x, y;
...
double d = static_cast<double>(x)/y; // x y로 나눕니다그러나 이때 부동소수점 나눗셈을 사용합니다.

Int 타입의 x double 타입으로 캐스팅한 부분에서 코드가 만들어집니다그것도 거의 항상 그렇습니다왜냐하면 대부분의 컴퓨터 아키텍처에서 int의 표현구조와 double의 표현구조가 아예 다르기 때문입니다.

class Base { ... };
class Derived: public Base { ... };
Derived d;
Base *pb = &d; // Derived* => Base*의 암시적 변환이 이루어집니다.

파생 클래스 객체에 대한 기본 클래스 포인터를 만드는(초기화하는), 흔한 코드입니다.그런데 두 포인터의 값이 같지 않을 때도 가끔 있다는 사실아십니까?이런 경우가 되면포인터의 변위(offset) Derived* 포인터에 적용하여 실제의 Base* 포인터 값을 구하는 동작이 바로 런타임(runtime)에 이루어집니다.

객체 하나(이를테면 Derived 타입의 객체)가 가질 수 있는 주소가 오직 한 개가 아니라그 이상이 될 수 있음을 (Base* 포인터로 가리킬 때의 주소, Derived* 포인터로 가리킬 때의 주소볼 수 있습니다.이런 현상은 C, 자바, C# 에서는 결코 생길 수 없지만 C++에서는 생깁니다.사실 C++에서는 다중 상속이 사용되면 이런 현상이 항상 생기지만심지어 단일 상속인데도 이렇게 되는 경우가 있습니다.

객체의 메모리 배치구조를 결정하는 방법과 객체의 주소를 계산하는 방법은 컴파일러마다 천차만별입니다어떤 플랫폼에서 메모리 배치를 다 꿰고 있어서’ 캐스팅을 했을 때 문제가 없었을지라도다른 플랫폼에서 그게 또 통하지는 않는다는 이야기죠.

가상 함수를 파생 클래스에서 재정의해서 구현할 때기본 클래스의 버전을 호출하는 문장을 가장 먼저 넣어달라는 요구사항을 보게 됩니다.

class Window { // 기본클래스
public:
           virtual void onResize() {...} // 기본클래스의 onResize구현결과
           ...
};

class SpecialWindow: public Window { // 파생클래스
public:
           virtual void onResize() { // 파생클래스의 onResize 구현결과
                     static_cast<Window>(*this).onResize(); // *this Window로 캐스팅하고 그것에 대해 onResize를 호출합니다동작이 안됩니다.
                     ... // SpecialWindow에서만 필요한 작업을 여기서 수행합니다.
           }
           ...
};

캐스트 부분은 *this Window로 캐스팅하는 코드다이에 따라 호출되는 onResize() Window::onResize 가 됩니다그런데함수 호출이 이루어지는 객체가현재의 객체가 아닙니다.
이 코드에서는 캐스팅이 일어나면서 *this의 기본 클래스 부분에 대한 사본이 임시적으로 만들어지게 되어 있는데지금의 onResize는 바로 이 임시 객체에서 호출된 것입니다.

이 문제를 풀려면 일단 캐스팅을 빼버려야 합니다.
class SpecialWindow: public Window { // 파생클래스
public:
           virtual void onResize() { // 파생클래스의 onResize 구현결과
                     Window::onResize(); // *this에서
                     ... // Window::onResize를 호출합니다.
           }
           ...
};

dynamic_cast, 말도 많고 탈도 많은 연산자입니다지금은 상당수의 구현환경에서 이 연산자가 정말 느리게 구현되어 있다.

dynamic_cast 연산자를 쓰고 싶어지는 때가 있다파생 클래스 객체임이 분명한 녀석이 있어서이에 대해 파생 클래스의 함수를 호출하고 싶은데그 객체를 조작할 수 있는 수단으로 기본 클래스의 포인터 (혹은 참조자밖에 없을 경우는 적지 않게 생기거든요이런 문제를 피해가는 일반적인 방법으로는 2가지를 들 수 있습니다.

첫번째 방법은파생 클래스 객체에 대한 포인터(혹은 스마트 포인터(13))를 컨테이너에 담아둠으로써각 객체를 기본 클래스 인터페이스를 통해 조작할 필요를 아예 없애 버리는 것입니다아래처럼 하지 말고

class Window {...};
class SpecialWindow: public Window {
public:
           void blink();
           ...
};

typedef std::vector<std::tr1::shared_ptr<Window> > VPW; // tr1::shared_ptr (13)
VPW winPtrs;
...
// 그다지 바람직스럽지 않은 코드: dynamic_cast를 쓰고 있습니다.
for (VPW::iterator iter = winPtrs.begin();
           iter != winPtrs.end();
           ++iter) {
           if (SpecialWindow *psw = dynamic_cast<SpecialWindow*>(iter->get()))
                     psw->blink();
}

다음과 같이 해 보라는 거죠.

typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW;
VPSW winPtrs;
...
// 더 괜찮은 코드: dynamic_cast가 없습니다.
for (VPSW::iterator iter = winPtrs.begin();
           iter != winPtrs.end();
           ++iter) {
           (*iter)->blink();
}

이 방법으로는 Window에서 파생될 수 있는모든 포인터를 똑같은 컨테이너에 저장할 수는 없습니다.

한편, Window 에서 뻗어 나온 자손들을전부 기본 클래스 인터페이스를 통해 조작할 수 있는 다른 방법이 없는 것은 아닙니다여러분이 원하는 조작을 가상 함수 집합으로 정리해서 기본 클래스에 넣어두면 됩니다예를 들어지금은 blank() SpecialWindow에서만 가능하지만아무것도 안하는 기본 blink()를 구현해서 가상합수로 제공하는 것입니다.

class Window {
public:
           virtual void blink() {...} // 기본구현은 '아무 동작 안하기입니다.
           ...
};

class SpecialWindow: public Window {
public:
           virtual void blink() {...} // 이 클래스에서는 blink가 특정한 동작을 수행합니다.
                     ...
           }
           ...
};

typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs; // 이 컨테이너는 Window에서 파생된 모든 타입의 객체에 대한 포인터들을 담습니다.

for (VPW::iterator iter = winPtrs.begin();
           iter != winPtrs.end();
           ++iter) {
           (*iter)->blink(); // dynamic_cast가 없습니다.

 2가지 방법중 어떤 것도(타입 안전성을 갖춘 컨테이너를 쓰든지가상함수를 기본클래스 쪽에 올려두든지모든 상황에 다 적용하기란 불가능하지만상당히 많은 상황에서 dynamic_cast를 쓰는 방법 대신에 꽤 잘 쓸 수 있습니다.

정말 피해야 하는 설계가 있습니다바로 폭포식(cascading) dynamic_cast라고 불리는 구조인데,

class Window {...};
... // 파생 클래스가 여기서 정의됩니다.
typedef std::vector<std::tr1::shared_ptr<Window> > VPW;
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
           if (SpecialWindow1 *psw1 = dynamic_cast<SpecialWindow1*>(iter->get())) {...}
           else if (SpecialWindow2 *psw2 = dynamic_cast<SpecialWindow2*>(iter->get())) {...}
           else if (SpecialWindow3 *psw3 = dynamic_cast<SpecialWindow3*>(iter->get())) {...}
...
}

이런 C++ 코드 때문에 C++가 욕을 먹는 것입니다.크기만 하고 아름답지 않으며속도도 둔한데다가망가지기 쉬운 코드가 만들어지거든요.
Window 
클래스 계통이 바뀌었다는 소식이라도 들렸다 치면항상 이런 코드는 또 뭐 넣고 뺄거 없나?’ 하는 검토 대상이 되니까 말이죠 (파생 클래스가 하나 추가되면위의 폭포식 코드에 계속해서 조건 분기문에 우겨 넣어야 합니다
).이런 코드는 가상 함수 호출에 기반을 둔 어떤 방법이든 써서 바꿔 놓아야 합니다.

캐스팅 역시그냥 막 쓰기에는 꺼림직한 문법 기능을 써야 할 때흔히 쓰이는 수단을 활용해서 처리하는 것이 좋습니다쉽게 말해 최대한 격리시키라는 것입니다캐스팅을 해야 하는 코드를 내부 함수 속에 몰아 놓고그 안에서 일어나는 천한일들을 이 함수를 호출하는 외부에서 알 수 없도록 인터페이스로 막아두는 식으로 하면 됩니다.

l  다른 방법이 가능하다면 캐스팅은 피하십시오특히 수행 성능에 민감한 코드에서 dynamic_cast는 몇 번이고 다시 생각하십시오설계 중에 캐스팅이 필요해졌다면캐스팅을 쓰지 않는 다른 방법을 시도해 보십시오.
l  캐스팅이 어쩔 수 없이 필요하다면함수 안에 숨길 수 있도록 해 보십시오.이렇게 하면 최소한 사용자는 자신의 코드에 캐스팅을 넣지 않고 이 함수를 호출할 수 있게 됩니다.
l  구형 스타일의 캐스트를 쓰려거든 C++ 스타일의 캐스트를 선호하십시오.발견하기도 쉽고설계자가 어떤 역할을 의도했는지가 더 자세하게 드러납니다.

[Effective C++] 항목 26 : 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자.

생성자 혹은 소멸자를 끌고 다니는 타입으로 변수를 정의하면반드시 물게 되는 비용이 2개 있습니다하나는 프로그램 제어 흐름이 변수의 정의에 닿을 때 생성자가 호출되는 비용이고,또 하나는 그 변수가 유효범위를 벗어날 때 소멸자가 호출되는 비용입니다.변수가 정의됐으나 사용되지 않은 경우에도 비용이 부과되는데이런 비용은 웬만한 경우가 아니면 물고 싶을 생각이 안 들 것입니다.

아래 예,이 함수는 주어진 비밀번호가 충분히 길 경우에 비밀번호를 암호화하여 반환하는 함수입니다.비밀번호가 너무 짧으면 logic_error 타입의 예외를 던지도록 만들어졌는데,
logic_error 
타입은 표준 C++ 라이브러리에 정의되어 있습니다 (54)

// 이 함수는 "encrypted" 변수를 너무 일찍 정의해 버립니다.
std::string encryptPassword(const std::string& password)
{
           using namespace std;
          
           string encrypted;
          
           if (password.length() < MinimumPasswordLength) {
                     throw logic_error("password is too short");
           }
           ... // 주어진 비밀번호를 암호화하여 encrypted 변수에 넣는데 필요한 일들을 여기서 합니다.
          
           return encrypted;
}

Encrypted 객체가 이 함수에서 완전히 안 쓰인다고는 말할 수 없지만예외가 발생되면 이 변수는 분명히 사용되지 않게 됩니다, encryptPasword 함수가 예외를 던지더라도 encrypted 객체의 생성과 소멸에 대해 비용을 내야 한다는 이야기입니다.이런 사정을 확인한 이상, encrypted 변수를 정의하는 일은 꼭 필요해지기 전까지로 미루는 편이 낫겠다는 생각이 들겠지요.

// 이 함수는 "encrypted" 변수가 진짜로 필요해질 때까지 정의를 미룹니다.
std::string encryptPassword(const std::string& password)
{
           using namespace std;
          
           if (password.length() < MinimumPasswordLength) {
                     throw logic_error("password is too short");
           }
          
           string encrypted;
          
           ... // 주어진 비밀번호를 암호화하여 encrypted 변수에 넣는데 필요한 일들을 여기서 합니다.
          
           return encrypted;
}

Encrypted 변수가 정의될 때 초기화 인자가 하나도 없습니다기본 생성자가 호출될 거란 뜻이지요.객체를 기본 생성하고 나서값을 대입하는 방법이 어째서 여러분이 원하는 값으로 직접 초기화하는 방법보다 효율이 좋지 않은지는 (4)를 확인.

// "encrypted" 를 정의하고 초기화하는 가장 좋은 방법.
std::string encryptPassword(const std::string& password)
{
           ... // 길이를 점검합니다.
          
           std::string encrypted(password); // 변수를 정의함과 동시에 초기화합니다.
          
           encrypt(encrypted);
           return encrypted;
}
어떤 변수를 사용해야 할 때가 오기 전까지그 변수의 정의를 늦추는 것은 기본이고초기화 인자를 손에 넣기 전까지 정의를 늦출 수 있는지도 둘러봐야 한다.
이렇게 해야 쓰지도 않을 객체가 만들어졌다 없어지는 일이 생기지 않으며불필요한 기본 생성자 호출도 일어나지 않습니다덤으로누가 보아도 그 변수의 의미가 명확한 상황에서 초기화가 이루어지기 때문에,변수의 쓰임새를 문서화하는데도 도움이 됩니다.

루프에 대해서는?어떤 변수가 루프 안에서만 쓰이는 경우라면해당 변수를 루프 바깥에서 미리 정의해 놓고 루프 안에서 대입하는 방법이 좋을까요아니면 루프 안에 변수를 정의하는 방법이 좋을까요?

// 방법뤂프 바깥쪽에 정의
Widget w;
for (int i = 0; i < n; i++) {
           w = i에 따라 달라지는 값;
           ...
}

// 방법뤂프 안쪽에 정의
for (int i = 0; i < n; i++) {
           Widget w(i에 따라 달라지는 값);
           ...
}

Widget 객체에 들어가는 연산을 기준으로 해서, 2방법에 걸리는 비용을 정리해 보죠.그 결과는 다음과 같다.
l  방법생성자1 + 소멸자1 + 대입n
l  방법생성자n + 소멸자n.

클래스 중에는 대입에 들어가는 비용이생성자-소멸자 쌍보다 적게 나오는 경우가 있는데, Widget 클래스가 이런 종류에 속한다면 A 방법이 훨씬 효율이 좋습니다이 차이는 n이 커질 때 특히 커집니다.반면그렇지 않은 경우엔 B 방법이 아마 더 좋을 것이고요

A방법을 쓰면 w라는 이름을 볼 수 있는 유효범위가 B방법을 쓸 때보다 넓어지기 때문에(루프를 포함하는 유효범위가 되죠), 프로그램의 이해도와 유지보수성이 역으로 안 좋아질 수도 있습니다.

정리.
1.     대입이 생성자-소멸자 쌍보다 비용이 덜 들고
2.     전체 코드에서 수행 성능에 민감한 부분을 건드리는 중 이라고 생각하지 않는다면,
B
방법으로 가는 것이 좋습니다
.

l  변수 정의는 늦출 수 있을 때까지 늦춥시다프로그램이 더 깔끔해지며 효율도 좋아집니다.

[Effective C++] 항목 25 : 예외를 던지지 않는 swap에 대한 지원도 생각해 보자

Swap은 초창기부터 STL에 포함된 이래로 예외 안전성 프로그래밍(29)에 없어선 안될 감초 역할로서자기대입 현상(11)의 가능성에 대처하기 위한 대표적인 메커니즘으로서 널리 사랑 받아 왔다.하지만 이 함수에 관련된 특이한 말썽거리들도 도사리고 있다.

두 객체의 값을 맞바꾸기(swap)’ 한다는 것은각자의 값을 상대방에게 주는 동작입니다.기본적으로는 이 맞바꾸기 동작을 위해표준 라이브러리에서 제공하는 swap 알고리즘을 쓰는데이 알고리즘이 구현된 모습을 보면 여러분이 알고 있는 그 ‘swap’과 하나도 다르지 않다는 것을 알 수 있다.

namespace std {
           templatee<typename T> // std::swap의 전형적인 구현
           void swap(T& a, T& b) // a의 값과ㅏ b의 값을 맞바꿉니다.
           {
                     T temp(a);
                     a = b;
                     b = temp;
           }
}

표준에서 기본적으로 제공하는 swap(이하 표준 swap)은 구현 코드와 같이 복사만 제대로 지원하는(복사 생성자 및 복사 대입 연산자를 통해타입이기만 하면 어떤 타입의 객체이든 맞바꾸기 동작을 수행해 줍니다. Swap을 위해 특별히 추가 코드를 마련하거나 할 필요가 없습니다.

표준 swap의 동작은 한번 호출에 복사가 3번 일어납니다타입에 따라서는 이런 temp 사본이 필요 없는 경우도 있다.

복사하면 손해를 보는 타입들 중 으뜸은다른 타입의 실제 데이터를 가리키는 포인터가 주성분(!)일 것이다.이러한 개념을 설계의 미학으로 끌어올려 많이들 쓰고 있는 기법이 바로 pimpl (pointer to implementation) 이지요.

pimpl 설계를 차용하여 Widget 클래스를 만든 예
class WidgetImpl { // Widget 의 실제 데이터를 나타내는 클래스.
public:
           ...
          
private:
           int a, b, c,;
           std::evctor<double> v;
           ..
};;

class Widget { // pimpl 관용구를 사용한 클래스
public:
           Widget(const Widget& rhs);
           Widget& operator=(const Widget& rhs)
           {
                     // Widget을 복사하기 위해 자신의 WidgetImpl 객체를 복사합니다.
                     // operator=의 일반적인 구현 방법에 대한 자세한 사항은 (10),(11),(12)
                     ...
                     *pImpl = *(rhs.pImpl);
                     ...
           }
           ...
private:
           WidgetImpl *pImpl; // Widget의 실제 데이터를 가진 객체에 대한 포인터.
};

이렇게 만들어진 Widget 객체를 우리가 직접 맞바꾼다면, pImpl 포인터만 살짝 바꾸는 것 말고는 실제로 할 일이 없습니다하지만 표준 swap 알고리즘은 Widget 객체 3개를 복사하고 WidgetImpl 객체 3개도 복사할 것입니다.

Widget 객체를 맞바꿀 때는 일반적인 방법을 쓰지 말고내부의 pImpl 포인터만 맞바꾸라고 std::swap에게 뭔가를 알려줍니다.
C++
에서 std::swap을 Widget에 대해 특수화(specialize) 하는 것.

namespace std {
           template<>
           void  swap<Widget>(Widget& a, Widget& b)
           {
                     // 이 코드는 T Widget일 경우에 대해
                     // std::swap을 특수화한 것입니다.
                     swap(a.pImpl, b.pImpl); // Widget 'swap'하기 위해각자의 pImpl만 맞바꿈.
           }
}

함수 시작부분에 있는 ‘template<>’.
이 함수가 std::swap의 완전 템플릿 특수화(total template specialization) 함수라는 것을 컴파일러에게 알려주는 부분입니다.그리고 함수 이름 뒤에 있는 ‘<Widget>’ T Widget일 경우에 대한 특수화라는 사실을 알려주는 부분.타입에 무관한 swap 템플릿이 Widget에 적용될 때는 위의 함수 구현을 사용해야 한다는 뜻.
일반적으로 std 네임스페이스의 구성요소는 함부로 변경하거나 할 수 없지만프로그래머가 직접 만든 타입(Widget )에 대해표준 템플릿(swap 같은)을 완전 특수화하는 것은 허용이 됩니다.

위 함수는 컴파일되지 않는다문법이 틀린 것이 아니라, a b에 들어 있는 pImpl 포인터에 접근하려고 하는데이들 포인터가 private 멤버이기 때문이다특수화 함수를 프렌드로 선언할 수도 있지만이렇게 하면 표준 템플릿들에 쓰인 규칙과 어긋나므로 좋은 모양은 아니다그래서 Widget 안에 swap이라는 public 멤버 함수를 선언하고그 함수가 실제 맞바꾸기를 수행하도록 만든 후에, std::swap 의 특수화 함수에게 그 멤버 함수를 호출하는 일을 맡깁니다.

clas Widget { // 앞의 예와 같은데, swap 멤버 함수가 추가된 것만 다름.
public:
           ...
           void swap(Widget& other)
           {
                     using std::swap; // 이 선언문이 필요한 이유는 이후의 설명에서 확인가능.
                               
                     swap(pImpl, other.pImpl); // Widget을 맞바꾸기 위해 Widget pImpl 포인터를 맞바꿉니다.
           }
           ...
};

namespace std {
           template<>
           void  swap<Widget>(Widget& a, Widget& b)
           {
                     // std::swap 템플릿의 특수화 함수를 살짝 고친 결과.
                     a.swap(b); // Widget을 맞바꾸기 위해, swap 멤버함수 호출.
           }
}

컴파일 되고기존의 STL 컨테이너와 일관성도 유지되는 착한 코드가 되었습니다.
public 
멤버 함수 버전의 swap과 이 멤버함수를 호출하는 std::swap의 특수화 함수 모두 지원하고 있고요.

만약, Widget과 WidgetImpl이 클래스가 아니라 클래스 템플릿으로 만들어져 있어서, WidgetImpl에 저장된 데이터의 타입을 매개변수로 바꿀 수 있다면 어떻게 될까요?

template<typename T>
class WidgetImpl {...};

template<typename T>
class Widget {...};

Swap 멤버 함수를 Widget (필요하면 WidgetImpl에도넣는 것은 어렵지 않으나,
std::swap
을 특수화하는 데서 좌절입니다.

작성하려던 코드는 이런 것이었으니까요.
namespace std {
           template<typename T>
           void swap<Widget <T> >(Widget<T>&a, Widget<T>& b) // 에러적법하지 않은 코드.
           {
                     a.swap(b);
           }
}

C++의 기준에는 적법하지 않습니다.지금 함수 템플릿(std::swap)을 부분적으로 특수화해 달라고 컴파일러에게 요청한 것인데,
C++
는 클래스 템플릿에 대해서는 부분 특수화(partial specialization)를 허용하지만
함수 템플릿에 대해서는 허용하지 않도록 정해져 있습니다.
(
함수 템플릿의 부분 특수화를 받아들이는 어리버리 컴파일러도 있긴 합니다).

함수 템플릿을 부분적으로 특수화하고 싶을 때흔히 취하는 방법은그냥 오버로드 버전을 하나 추가하는 것입니다.

namespace std {
           template<typename T>
           void swap(Widget<T>&a, Widget<T>& b)
           {
                     // std::swap을 오버로드한 함수.
                     // "swap" 뒤에 "<...>"가 없는 것에 주의.
                     // 이 함수가 왜 유효하지 않은지는 뒤에...
                     a.swap(b);
           }
}

일반적으로 함수 템플릿의 오버로딩은 해도 별 문제가 없지만,
std
는 조금 특별한 네임스페이스이기 때문에규칙도 다소 특별합니다
., std 내의 템플릿에 대한 완전 특수화는 OK이지만,
std
에 새로운 템플릿을 추가하는 것은 NOK입니다 (혹은 클래스이든 함수이든 어떤 것도 안됩니다
).
std
에 들어가는 구성요소의 결정은 전적으로 C++ 표준화 위원회에 달려 있기 때문에금지하고 있는 것이죠
.그런데그 금지하는 모양이 사람 마음을 불편하게 하는데, std의 영역을 침범하더라도 일단 컴파일까지는 거의 다 되고 실행도 됩니다그런데 실행되는 결과가 미정의 사항이라는 것입니다.

사용자들이 swap을 호출해서 우리만의 효율 좋은 템플릿 전용 버전을 쓰고 싶다방법은멤버 swap을 호출하는 비멤버 swap을 선언해 놓되이 비멤버 함수를 std::swap의 특수화 버전이나 오버로딩 버전으로 선언하지만 않으면 됩니다.
예를 들어, Widget 관련 기능이 WidgetStuff 네임스페이스에 들어 있다고 가정하면다음과 같이 만들라는 것입니다.

namespace WidgetStuff {
           ... // 템플릿으로 만들어진 WidgetImpl 및 기타등등.
           template<typename T> // 이전과 마찬가지로 swap이란 이름의 멤버 함수가 들어 있습니다.
           class Widget { ... };
           ...
           template<typename T> // 비멤버 swap 함수.
           void swap(Widget<T>& a, Widget<T>& b) // std의 일부가 아님.
           {
                     a.swap(b);
           }
}

이제는 어떤 코드가 두 Widget 객체에 대해 swap을 호출하더라도컴파일러는 C++의 이름 탐색 규칙 
[
이 규칙은 인자 기반 탐색(argument-dependent lookup) 혹은 쾨니그 탐색(Koenig lookup)이란 이름으로 알려져 있습니다] (어떤 함수에 어떤 타입의 인자가 있으면그 함수의 이름을 찾기 위해해당 타입의 인자가 위치한 네임스페이스 내부의 이름을 탐색해 들어간다는 간단한 규칙이다. ADL(Argument Dependent Lookup)이란 약자로 많이 불린다. ‘쾨니그으이 Koenig C++ 표준화 위원회 임원이자이 규칙을 창안한 앤드류 쾨니그(Andrew Koenig)의 이름에서 따온 것이다)에 의해 WidgetStuff 네임스페이스 안에서 Widget 특수화 버전을 찾아냅니다.

이 간단한 방법은 클래스 템플릿뿐만 아니라클래스에 대해서도 잘 통합니다.클래스에 대해 std::swap을 특수화해야 할 이유가 생길 때여러분이 만든 클래스 타입 전용의 swap’이 되도록 많은 곳에서 호출되도록 만들고 싶으시면 (그리고 그런 swap을 갖고 있다면), 그 클래스와 동일한 네임스페이스 안에비멤버 버전의 swap을 만들어 넣고그와 동시에 std::swap의 특수화 버전도 준비해 둬야 합니다.

위의 모든 사항들은 여러분이 네임스페이스를 안 쓰고 있어도여전히 유효합니다 (멤버 swap을 호출하는 비멤버 swap이 필요하다는 뜻). 하지만 전역 네임스페이스(global namespace)를 못 잡아먹어서 안달복달할 필요가 있을까요클래스템플릿함수나열자 타입나열자 상수, typedef 등의 온갖 이름을 전역 네임스페이스에 들이대면서 말이죠.

다음 함수 템플릿은 실행 중에 swap을 써서 2 객체의 값을 맞바꾸어야 한다고 가정합니다.

template<typename T>
void doSomething(T& obj1, T& obj2)
{
           ...
           swap(obj1, obj2);
           ...
}

이 부분에서 어떤 swap을 호출해야 할까요가능성은 3가지 입니다.
1.     Std에 있는 일반형 버전 (이것은 확실히 있습니다)
2.     Std의 일반형을 특수화한 버전 (있을 수도없을 수도 있습니다)
3.     타입 전용의 버전 (있거나 없거나 할 수 있으며어떤 네임스페이스안에 있거나 없거나 할 수 있습니다(하지만 확실히 std 안에는 없어야 하겠지요))
여러분은 타입 T 전용 버전이 있으면 그것이 호출되도록 하고,
타입 전용 버전이 없으면 std의 일반형 버전이 호출되도록 만들고 싶습니다
.어떻게 할 수 있을까요아래 코드가 답입니다.

template<typename T>
void doSomething(T& obj1, T& obj2)
{
           using std::swap; // std::swap을 이 함수 안으로 끌어올 수 있도록 만드는 문장
           ...
           swap(obj1, obj2); // T 타입 전용의 swap을 호출합니다.
           ...
}

컴파일러가 위의 swap 호출문을 만났을 때 하는 일은현재의 상황에 딱 맞는 swap을 찾는 것입니다.
C++
의 이름 탐색 규칙에 따라우선 전역 유효범위 혹은 타입 T와 동일한 네임스페이스 안에 T전용의 swap이 있는지 찾습니다
.
[
예를 들어, T WidgetStuff 네임스페이스 내의 Widget 이라면
컴파일러는 인자 의존 규칙을 적용하여 WidgetStuff swap을 찾아낼 것입니다]
전용 swap이 없으면컴파일러는 그 다음 순서를 밟는데이 함수가 std::swap을 볼 수 있게 해 주는 using 선언(using declaration)이 함수 앞부분에 있기 때문에, std swap을 쓰게끔 결정할 수도 있습니다
.하지만 이런 상황이 되더라도컴파일러는 std::swap T 전용 버전을일반형 템플릿보다 더 우선적으로 선택하도록 정해져 있기 때문에, T에 대한 std::swap의 특수화 버전이 이미 준비되어 있으면 결국 그 특수화 버전이 쓰이게 됩니다.

원하는 swap이 호출되도록 만드는 작업은 별로 어렵지 않습니다.이거 딱 하나만 조심하면 됩니다호출문에 한정자를 잘못 붙이거나 하지는 마세요.한정자가 붙게 되면, C++가 호출될 함수를 결정하는 메커니즘에 바로 영향이 가기 때문입니다.

예를 들어위의 호출문을 아래와 같이 써버리면,
std::swap(obj1, obj2) // swap을 호출하는 잘못된 방법.
Std swap(그 어떤 템플릿 특수화 버전들도 포함해서외의 다른 것은 거들떠보지도 말라고 컴파일러를 구속하게 됩니다더 딱 맞을 수 있는 T 전용 버전이 다른 곳에 있을 가능성을 완전히 무시하는 것이죠.클래스에 대해 std::swap을 완전히 특수화하는 게 중요한 이유가 바로 이것입니다.이렇게 해두면 잘못 한정화된 호출문으로도 타입 T 전용의 swap 함수를 끌어와 쓸 수 있기 때문입니다 (시중의 표준 라이브러리 중에도 이런 코드가 들어 있는 예가 꽤 있기 때문에이런 코드가 가능한 효율적으로 동작하는 데 도움을 주는 편이 여러분에게 이익이 됩니다)

표준 swap, 멤버 swap, 비멤버 swap, 특수화한 std::swap, swap호출시의 상황.정리,
첫째표준에서 제공하는 swap여러분의 클래스 및 클래스 템플릿에 대해 납득할 만한 효율을 보이면그냥 아무것도 하지 말고 지내세요여러분이 만든 타입으로 만든 객체에 대해 ‘swap’을 시도하는 사용자 코드는 표준 swap을 호출하게 될 것입니다그리고 아무 문제도 없을 거고요.

둘째그런데 표준 swap의 효율이 기대한 만큼 충분하지 않다면 (여러분의 클래스 혹은 클래스 템플릿이 pimpl 관용구와 비슷하게 만들어져 있을 경우가 십중팔구입니다), 다음과 같이 하십시오
.
1.     여러분의 타입으로 만들어진 2객체의 값을 빛나게 빨리 맞바꾸는 함수를 swap이라는 이름으로 만들고이것을 public 멤버 함수로 두십시오이 함수는 절대로 예외를 던져서는 안됩니다.
2.     여러분의 클래스 혹은 템플릿이 들어 있는 네임스페이스와 같은 네임스페에스에 비멤버 swap을 만들어 넣습니다그리고 1번에서 만든 swap 멤버 함수를 이 비멤버 함수가 호출하도록 만듭니다.
3.     새로운 클래스(클래스 템플릿이 아니라)를 만들고 있다면그 클래스에 대한 std::swap의 특수화 버전을 준비해 둡니다그리고 이 특수화 버전에서도 swap 멤버 함수를 호출하도록 만듭니다.

셋째사용자 입장에서 swap을 호출할 때, swap을 호출하는 함수가 std::swap을 볼 수 있도록 using 선언을 반드시 포함시킵니다그 다음에 swap을 호출하되네임스페이스 한정자를 붙이지 않도록 하십시오.

멤버 버전의 swap은 절대로 예외를 던지지 않도록 만들라고 했습니다그 이유는 swap을 진짜 쓸모 있게 응용하는 방법들 중에 
클래스(및 클래스 템플릿)가 강력한 예외 안전성 보장(string exception-safety guarantee, 어떤 연산이 실행되다가 예외가 발생되면 그 연산이 시작되기 전의 상태로 돌릴 수 있다는 보장)을 제공하도록 도움을 주는 방법이 있기 때문입니다(29).그런데 이 기법은 멤버 버전 swap이 예외를 던지지 않아야 한다는 가정을 깔고 있습니다멤버 버전만 이렇습니다.비멤버 버전의 경우표준 swap은 복사 생성과 복사 대입에 기반하고 있는데일반적으로 복사 생성 및 복사 대입 함수는 예외 발생이 허용되기 때문에 이런 제약을 받지 않습니다.따라서 swap을 직접 만들 분은 2값을 빠르게 바꾸는 방법만 구현하고 끝내면 안됩니다예외를 던지지 않는 방법도 함께 준비하는 센스가 필요합니다다행히 효율과 예외 금지 2가지 특성은 함께 붙어 다니는 경우가 대부분입니다.효율이 대단히 좋은 swap 함수는 거의 항상 기본제공 타입(pimpl 관용구 기반의 설계에서 쓰이는 포인터처럼을 사용한 연산으로 만들어지기 때문입니다그리고 기본제공 타입을 사용한 연산은 절대로 예외를 던지지 않거든요.

l  std::swap이 여러분의 타입에 대해 느리게 동작할 여지가 있다면, swap 멤버함수를 제공합시다이 멤버 swap은 예외를 던지지 않도록 만듭시다.
l  멤버 swap을 제공했으면이 멤버를 호출하는 비멤버 swap도 제공합니다.클래스(템플릿이 아닌)에 대해서는, std::swap도 특수화해 둡시다.
l  사용자 입장에서 swap을 호출할 때는, std::swap에 대한 using 선언을 넣어 준 후에 네임스페이스 한정 없이 swap을 호출합시다.
l  사용자 정의 타입에 대한 std 템플릿을 완전 특수화하는 것은 가능합니다그러나 std에 어떤 것이라도 새로 추가하려고 들지는 마십시오.

[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 인라인 함수의 아이디어는  함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것  남발했다가는 코드의 크기가 커질 게 뻔하다. 인라인 함수로 부풀려진 ...