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

Bogoomi·2022년 6월 20일
0

EffectiveC++

목록 보기
10/24

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


요약
평소에 사용하는 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 선언을 반드시 포함하자.

느낀점
이번항목은 꽤 어려운부분인게 느껴진다.(진짜 이해하려고 같은 곳을 여러번 봐야했다 ㅋㅋ)
그래서 세세한 이해보다는 이런방법도 있다는 식으로 알아두는게 좋을 듯하다.

profile
개에에에바알

0개의 댓글