독서노트Effective_C++_조항25: 이상을 던지지 않는 swap 함수를 작성하는 것을 고려합니다

16285 단어 effective
나도 왜 작가가 이 조항에 이런 이름을 지었는지 모르겠다. 왜냐하면 이렇게 보면'이상을 던지지 않는다'는 데 중점을 두고 있기 때문이다. 그러나 사실 작가는 전문의 마지막 부분에서 이상을 던지지 않는 원인을 말했을 뿐이다. 대부분의 단락은 자원을 절약하는 swap 함수를 어떻게 쓰는지 소개하는 것이다.
헤더 파일인 iostream만 포함하면 swap 함수를 사용할 수 있습니다. 예를 들어:
1 #include <iostream>
2 
3 int main()
4 {
5     int a = 3;
6     int b = 4;
7     std::swap(a, b);
8 }

결과는 a가 4, b가 3이다. 즉, std의 명명 공간에 이미 기존의 swap 함수가 있다. 이 swap 함수는 매우 간단하다. 이것은 이렇게 보인다.
1 template<class T>
2 void swap(T& a, T& b)
3 {
4     T c = a;
5     a = b;
6     b = c;
7 }

이것은 가장 흔히 볼 수 있는 형식의 두 수를 교환한 것이다(특히 T가 정수일 때 다른 조작을 사용할 수 있다. 이것은 본 조항 토론의 중점이 아니기 때문에 생략했지만 면접문제에서 이것을 묻는 것을 매우 좋아한다).
클래스 Element이 존재한다고 가정하면 클래스의 요소가 공간을 차지합니다.
1 class Element
2 {
3 private:
4     int a;
5     int b;
6     vector<double> c;
7 };

Sample 클래스의 개인 구성원은 Element의 바늘로 원본 바늘이 있으며 대부분의 경우 사용자 정의 분석 함수, 복사 구조 함수와 값 부여 연산자가 필요합니다. 아래와 같습니다.
1 class Sample
2 {
3 private:
4     Element* p;
5 public:
6     ~Sample();
7     Sample(const Sample&);
8     Sample& operator= (const Sample&);
9 };

operator=를 실현할 때 아주 좋은 실현 방법이 있다. 참고조항대략 이렇게:
1 Sample& operator= (const Sample& s)
2 {
3     if(this != &s)
4     {
5         Sample temp(s);
6         swap(*this, temp);
7     }
8     return *this;
9 }

자기가 값을 부여한 것이 아니라고 판단한 후에 복사 구조 함수를 호출하여 임시 대상을 만든다. (이곳에 이상이 있을 수 있다. 예를 들어 공간을 분배할 수 없다는 등) 만약에 이 대상이 이상으로 인해 성공적으로 만들어지지 않았다면 아래의 swap는 실행하지 않는다.this의 원시 값을 파괴하지 않을 것이다. 만약에 이 대상이 성공적으로 만들어진다면 swap는 잠시 후에 임시 대상의 값을 *this의 값으로 바꾼다.부가가치의 효과에 도달하다.
위의 설명은 조항 9의 내용입니다. 만약에 기억이 나지 않으면 뒤돌아볼 수 있습니다. 본 조항의 중점은 이 swap 함수에 있습니다.기본 std의 swap 함수를 호출합니다. 임시 Sample 대상 (복사 구조 함수) 을 만들고 두 번의 값 연산을 호출합니다. 이것은 swap 함수에서operator=를 호출하고, 이전에operator=에서 swap 함수를 호출합니다. 이것은 안 됩니다. 무한한 귀속을 초래하고 창고가 넘쳐납니다.
따라서, 우리는 자신의 swap 함수를 써야 한다. 이 함수는 Sample의 구성원을 교환하는 것이다.
문제가 또 생겼다. Sample에 저장된 것은 Element을 가리키는 바늘이다. 그것은 바늘을 교환하는 것이 좋을까, 아니면 바늘이 가리키는 대상 안의 내용을 하나하나 교환하는 것이 좋을까?Element 안에 물건이 매우 많기 때문에 분명히 바늘을 직접 교환하는 것이 좋다(본질은 Element 대상이 저장한 주소를 교환한 것이다).
따라서 swap의 구성원 함수를 정의할 수 있습니다.이렇게
 1 void swap(Sample& s)
 2 {
 3     std::swap(p, s.p);
 4 }
 5 Sample& operator= (const Sample& s)
 6 {
 7     if(this != &s)
 8     {
 9         Sample temp(s);
10         this->swap(s);
11     }
12     return *this;
13 }

그러나 이렇게 보면 좀 어색해 보인다. 우리는 습관적으로 swap(a, b)과 같은 형식의 swap을 사용하는데 다른 프로그래머에게 맡기면 클래스 밖에서 swap(SampleObj1, SampleObj2)처럼 사용하기를 원한다. SampleObj1이 아니라.swap(SampleObj2).이를 위해 우리는 std 공간에서 특화된 버전을 정의할 수 있다. (std namespace는 아무거나 추가할 수 없고 swap과 같은 특화된 버전만 추가할 수 있다.) 이렇게.
1 namespace std
2 {
3 template<>
4 void swap<Sample>(Sample &s1, Sample &s2)
5 {
6     s1.swap(s2); //            
7 }
8 }

operator=를 다시 쓰려면 다음과 같이 하십시오.
1 Sample& operator= (const Sample& s)
2 {
3     if(this != &s)
4     {
5         Sample temp(s);
6         swap(*this, s); //     ,          swap
7     }
8     return *this;
9 }

이렇게 하면 namespace std를 사용하는 곳에서 swap () 함수로 두 개의 Sample 대상을 교환할 수 있습니다.
Sample이 현재 템플릿 클래스라고 가정하면 Element도 템플릿 클래스입니다. 즉,
1 template <class T>
2 class Element
3 {…};
4 
5 template <class T>
6 class Sample
7 {…};

그럼 어떻게 해야 돼요?
템플릿 아래에서 std의 swap을 특화하는 것은 비합법적이다. (이것은 편특화라고 하고 컴파일러는 std에서 편특화를 허용하지 않는다.) 사용자 정의 공간에서만 정의할 수 있다. 예를 들어:
 1 namespace mysample
 2 {
 3     template <class T>
 4 class Element
 5 {…};
 6 
 7 template <class T>
 8 class Sample
 9 {…};
10 
11 template <class T>
12 void swap(Sample<T> &s1, Sample<T> &s2)
13 {
14     s1.swap(s2);
15 }
16 }

총괄적으로 말하면 일반 클래스일 때 swap의 특화 버전을 std의namespace에 놓을 수 있고 swap가 함수를 지정할 때 이 특화 버전을 우선적으로 호출할 수 있다.템플릿 클래스일 때, swap의 편특화 버전만 사용자 정의namespace에 넣을 수 있습니다.자, 질문이 왔습니다. 이때 swap(Sample Obj1, Sample Obj2)을 사용할 때 std 버전의 swap을 호출합니까, 아니면namespace의 swap을 사용자 정의합니까?
사실상 컴파일러는 사용자가 정의한 특화된 버전을 우선적으로 고려합니다. 이 버전이 호출 형식에 맞지 않을 때만 std의 swap을 호출할 수 있습니다.하지만 이때
1 Sample& operator= (const Sample& s)
2 {
3     if(this != &s)
4     {
5         Sample temp(s);
6         swap(*this, s); //    swap   std::
7     }
8     return *this;
9 }

안의 swap은 std::swap을 사용하지 마십시오. 왜냐하면 컴파일러는 Samplespace에 있는 편특화된 버전을 일부러 호출하지 않고 std 이름 공간을 강제로 호출한다고 생각할 것입니다.
이 오류를 방지하기 위해서, 책에서는 Sample가 일반 클래스일 때 std 명칭 공간에서 특화된 버전을 정의하는 것을 권장합니다.
이 조항은 약간 난이도가 있으니, 우리 총괄해 보자.
1. 클래스에public swap 구성원 함수를 제공합니다. 이 함수는 바늘 자체를 직접 교환합니다(바늘 자체가 int 형식이기 때문에 std의 일반 swap 함수를 호출합니다). 다음과 같습니다.
1 void Sample::swap(Sample &s)
2 {
3     swap(p, s.p); //      std::swap(this->p, s.p);
4 }

2. Sample과 같은 Namespace 공간에 non-member swap을 제공하고 구성원 함수에 있는 swap을 호출합니다. 다음과 같습니다.
1 template <>
2 void swap<Sample>(Sample& s1, Sample& s2){s1.swap(s2);} //   Sample    ,   swap  mysample   ,         std   (          ,       )

혹은
1 template <class T>
2 void swap(Sample<T>& s1, Sample<T>& s2){s1.swap(s2);} //   Sample     ,     mysample   

 
자, 마지막 부분에서 드디어 이상을 던지지 않는 문제를 언급했다. 책에서 언급한 것은 구성원 함수의 그 swap에서 이상을 던지지 말라는 것이다. 구성원 함수의 swap은 흔히 간단한 개인 구성원(지침 포함)의 교체이기 때문이다. 예를 들어 두 개의 int값을 교환하는 등은 모두 기본 유형이다. 이상을 던지지 않아도 된다. 이상을 던지는 임무는non-member의 swap에 맡긴다.
 
마지막으로 요약:
1. std::swap가 당신의 유형 효율이 높지 않을 때 swap 구성원 함수를 제공합니다. 이 구성원 함수는 이상을 던지지 않고 내장 형식만 조작합니다.
2.member swap을 제공하면non-member swap을 제공하여 전자를 호출해야 합니다. 일반 클래스에 대해서도 std::swap을 특화하십시오.
3. swap을 호출할 때 자신의 이름 공간을 호출하는 swap인지 std의 swap인지 구분하고 std::기호를 함부로 추가할 수 없습니다
4.'사용자 정의 유형'을 위한 std template 전특화는 좋지만, std에 새로운 것을 추가하는 것은 절대 시도하지 마세요.

좋은 웹페이지 즐겨찾기