책 정리/Effective C++ 3rd

항목 25. 예외를 던지지 않는 swap에 대한 지원도 생각해보자

ocean20 2020. 1. 13. 14:55

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 변경하려고는 하지마라

 

 

(이번 항목 정리는 템플릿에 대해 공부하느라 꽤 오래걸렸다.ㅜㅜ)

참고 사이트

  1. https://dogpaw-programming.tumblr.com/

  2. http://egloos.zum.com/sweeper/v/2998778