일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- UE4
- 비교 함수 객체
- 스마트 포인터
- 반복자
- implicit conversion
- more effective c++
- virtual function
- 게임
- 영화 리뷰
- Vector
- 참조자
- effective stl
- reference
- 루아
- operator new
- 메타테이블
- 다형성
- 오블완
- exception
- 언리얼
- Smart Pointer
- 암시적 변환
- resource management class
- Effective c++
- 티스토리챌린지
- 상속
- 예외
- c++
- 영화
- lua
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 25. 예외를 던지지 않는 swap 함수 본문
728x90
항목 25. 예외를 던지지 않는 swap에 대한 지원도 생각해보자
swap 함수
- 다양한 활용성
- 쓸모가 많기에 구현 방법이 중요하다.
- 그렇다면 어떻게 만들어야 쓸만한 swap을 만들 수 있을까?
STL이 제공하는 swap 함수의 구현
namespade std
{
template<typename T>
void swap(T& a, T& b)
{
T temp(a);
a = b;
b = temp;
}
}
- T 객체가 복사만 지원해준다면 정상 동작한다.
- 호출 1번, 복사 3번이 발생한다.
복사하면 손해를 보는 타입
- (다른 타입의 실제 데이터를 가리키는)포인터가 주성분인 경우
- ex) pimpl 관용구
-
class WidgetImpl // 여러 데이터를 포함. 즉, 복사 비용이 높다. { public: ... private: int a, b, c; std::vector<double> v; ... }; class Widget // pImpl 관용구를 사용한 클래스 { public: Widget(const Widget& rhs); Widget& operator=(const Widget& rhs) { ... *pImpl = *(rhs.pImpl); // WidgetImpl 객체를 복사 ... } ... private: WidgetImpl *pImpl; };
-
- 포인터만 바꾸면 되므로 복사시 자원 소모가 적다.
- 이 경우 swap 함수에서 실제로 하면 되는 일은?
- 존재하는 두 개의 Widget 객체 내부 pImpl만 교체하면 된다.
- 즉, WidgetImpl 포인터 세 개를 복사하여 swap하면 끝난다.
- 하지만 표준 swap 알고리즘의 구현은 아래와 같다.
- Widget 객체 세 개를 복사하여 swap한다.
- 즉 WidgetImpl 포인터 세 개를 포함하는 Widget 객체 세 개를 복사하므로 불필요한 자원 소모가 있다.
- ex) pimpl 관용구
swap 함수 보완법
- 템플릿 특수화(specialize)를 사용한다.
-
namespace std { // Widget 객체에 대한 템플릿 특수화 버전 template<> void swap<Widget>(Widget& a, Widget& b) { swap(a.pImpl, b.pImpl); // pImpl만 맞바꾼다. } }
- 문제점
- pImpl에 대한 접근 권한이 없는 경우가 많을 것이다.
- 이 경우 pImpl은 private 로 감춰져 있기에 컴파일이 되지 않는다.
- 프랜드 함수로 만들어 해결한다?
- 표준 템플릿들에 쓰인 규칙과 어긋나므로 좋은 모양은 아니다.
-
- 템플릿 특수화를 사용하되, 클래스가 swap 함수를 public으로 지원한다.
-
class Widget { public: ... void swap(Widget& other) // swap 함수 제공 { using std::swap; swap(pImpl, other.pImpl); } ... }; namespace std { // 템플릿 특수화 버전 template<> void swap<Widget>(Widget& a, Widget& b) { a.swap(b); // 클래스에서 제공하는 swap 함수를 사용 } }
- 문제점
- 아래처럼 객체가 클래스가 아니라 클래스 템플릿으로 만들어진 경우 위 코드는 컴파일이 되지 않는다..
-
template<typename T> class WidgetImpl { ... }; templcate<typename T> class Widget { ... };
- C++에서는 클래스 템플릿의 부분 특수화는 허용이 되지만 함수 템플릿의 부분 특수화는 허용이 되지 않는다.
- 완전 특수화 : 하나의 클래스에 대한 특수화
- 부분 특수화 : 다수의 클래스에 대한(즉, 또 다른 템플릿 클래스에 대한) 특수화
-
namespace std { template<typename T> void swap< Widget<T> >(Widget<T>& a, Widget<T>& b) // 이런 구성은 부분 특수화이므로 에러 { a.swap(b); } }
-
- 아래처럼 객체가 클래스가 아니라 클래스 템플릿으로 만들어진 경우 위 코드는 컴파일이 되지 않는다..
-
- 오버로드 버전을 하나 추가한다.
-
namespace std { // std::swap을 오버로드한 함수 template<typename T> void swap(Widget<T>& a, Widget<T>& b) { a.swap(b); } }
- 문제점
- std는 특별한 namespace이므로, 이 네임스페이스에 대한 규칙도 다소 특별하다.
- std 내의 템플릿에 대한 완전 특수화는 괜찮지만, std 내에 새로운 템플릿을 추가하는 것은 안된다.
- 클래스, 함수 어떤 것도 안된다.
- 컴파일은 될지라도 결과는 미정의 사항!!
- 그러므로 이 방식은 사용하면 안 된다.
-
- 멤버 swap을 호출하는 비멤버 swap을 선언하되, 커스텀 네임스페이스로 그 모든 걸 감싼다.
-
namespace WidgetStuff { template<typename T> class Widget { ... }; // 비멤버 swap 함수 선언 template<typename T> void swap(Widget<T>& a, Widget<T>& b) { a.swap(b); } }
- 해당 비멤버 swap은 C++의 이름 탐색 규칙(인자 기반 탐색 혹은 쾨니그 탐색)에 의해 std 버전보다 먼저 발견된다.
- 즉, 그 클래스와 동일한 네임스페이스 안에 비멤버 swap으로 선언하면 해결된다.
- 네임스페이스를 사용하지 않아도 위 사항은 유효하지만...
- 전역 네임스페이스에 온갖 이름을 때려넣는 건 관리 측면에서 안 좋으므로 가능하면 네임스페이스로 감싸자.
-
인자 기반 탐색(argument-dependent lookup)(ADL)
- 어떤 함수에 어떤 타입의 인자가 있으면, 그 함수의 이름을 찾기 위해 해당 타입의 인자가 위치한 네임스페이스 내부의 이름을 탐색해 들어간다는 규칙
함수 템플릿 내부에서 swap을 사용할 때 탐색 순서
template<typename T>
void doSomething(T& obj1, T& obj2)
{
using std::swap; // std::swap을 이 함수 안으로 끌어올 수 있도록 만든다.
...
swap(obj1, obj2); // 여기서 어떤 swap을 호출해야 할까?
// std::swap(obj1, obj2);
// 이 사용법은 다른 swap 함수의 가능성을 배제하므로 좋지 않다.
...
}
- 전역 유효범위 혹은 타입 T와 동일한 네임스페이스 안에 T 타입 전용의 swap 버전 탐색 (swap 함수 보완 4번 케이스)
- std의 일반형을 특수화한 버전 탐색 (swap 함수 보완 2번 케이스)
- 앞의 using 문으로 인해 std::swap을 볼 수 있으므로 탐색하게 된다.
- std에 있는 일반형 버전 (보편적인 케이스)
- 확실히 존재한다.
정리
- 표준 제공 swap이 효율이 괜찮으면 아무것도 하지 말자.
- 기대만큼의 효율이 없다면
- public swap 멤버 함수를 만든다.
- 이 경우 절대 예외를 던지면 안된다.
- 클래스(및 클래스 템플릿)가 강력한 예외 안전성 보장을 제공하도록 도움을 주는 방법이 있기 때문이다.(항목 29 참조)
- 해당 클래스 혹은 템플릿이 들어 있는 네임스페이스와 같은 네임스페이스에 비멤버 swap을 만들어 넣는다.
- 1번 멤버 함수를 내부적으로 호출하게 한다.
- 새로운 클래스(클래스 템플릿이 아닌 경우)를 만들고 있다면, 그 클래스에 대한 std::swap의 특수화 버전을 준비해 둔다.
- 1번 멤버 함수를 내부적으로 호출하게 한다.
- public swap 멤버 함수를 만든다.
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 27. 캐스팅 (0) | 2024.06.21 |
---|---|
[Effective C++] 26. 필요한 시점 직전에 변수를 정의할 것 (0) | 2024.06.20 |
[Effective C++] 24. 비멤버 함수 구현으로 암시적 변환 지원 (0) | 2024.06.18 |
[Effective C++] 23. 비멤버 비프렌드 함수 (0) | 2024.06.18 |
[Effective C++] 22. 데이터 멤버 확실히 숨기기 (0) | 2024.06.17 |
Comments