*** C++ 특수 멤버 함수들의 자동 작성 조건 ***
그래서 이동 생성자가 선언되어 있으면 컴파일러가 이동 배정 연산자를 작성하지 못하게 하고 이동 배정 연산자가 선언되어 있으면 컴파일러가 이동 생성자를 작성하지 못하게 한 것입니다.
더 나아가서, 복사 연산(생성 또는 배정)을 하나라도 명시적으로 선언한 클래스는 이동 연산들이 작성되지 않습니다. 그 근거는, 복사 연산을 선언했다는 것을 일반적인 객체 복사 방식(멤버별 복사)이 그 클래스에 대해 적합하지 않다는 뜻이고, 만일 복사 연산들에 대해 멤버별 복사가 적합하지 않다면 이동 연산들에 대한 멤버별 이동 역시 적합하지 않을 가능성이 크다는 것입니다.
반대 방향도 마찬가지입니다. 즉, 이동 연산(생성 또는 배정)을 하나라도 명시적으로 선언하면 컴파일러는 복사 연산들을 비활성화합니다. (구체적으로는 복사 연산들을 '삭제'해서 비활성화하는데, 함수의 삭제에 관해서는 Chapter(11)을 참고) 사실, 멤버별 이동이 적절한 객체 이동 방식이 아니라면, 멤버별 복사가 적절한 객체 복사 방식이라고 기대할 이유는 없습니다. 복사 연산들이 활성화되는 조건들이 C++98에 비해 C++11에서 더 제한적이라는 점을 생각하면 이런 규칙 때문에 C++98 코드가 제대로 컴파일되지 않을 것 같지만, 실제로는 그렇지 않습니다. 어차피 C++98은 객체의 '이동'이라는 것이 없으므로, C++98 코드에는 이동 연산이 있을 수 없습니다. 기존 클래스에 사용자 선언 이동 연산을 추가하는 유일한 방법은 그 클래스를 C++11의 요구에 맞게 고치는 ㄴ것뿐이며, 이동 의미론의 장점을 취하도록 수정된 클래스는 특수 멤버 함수의 작성에 관한 C++11 규칙들을 반드시 따라야 합니다.
아마 3의 법칙(Rule of Three)이라고 부르는 지침을 들어본 적이 있을 것입니다. 3의 규칙이란, 만일 복사 생성자와 복사 배정 연산자, 소멸자 중 하나라도 선언했다면 나머지 둘도(즉, 셋 다) 선언해야 한다는 것입니다. 이 지침의 근원은 이런 것입니다. 어떤 클래스의 복사 배정 연산의 의미를 프로그래머가 직접 지정해야 할 필요성은 거의 항상 그 클래스가 어떤 형태로든 자원 관리를 수행하기 때문에 생깁니다. 그리고 그런 클래스에서는 거의 항상, (1) 한 복사 연산이 수행하는 자원 관리를 다른 복사 연산에서도 수행해야 하며, (2) 클래스의 소멸자 역시 그 자원의 관리에 참여합니다(보통의 경우 자원을 해제). 관리되는 자원의 대표적인 예는 메모리이며, 표준 라이브러리에서 메모리를 관리하는 모든 클래스
(이를테면 동적 메모리 관리를 수행하는 STL 컨테이너들)가 이 '3대 멤버 함수', 즉 두 복사 연산과 소멸자를 모두 선언하는 것도 바로 이 때문입니다.
3의 규칙에 기초해서 추론하자면, 클래스에 사용자가 선언한 소멸자가 있다는 것은 그 클래스의 복사 연산들에 단순한 멤버별 복사가 적합하지 않음을 뜻할 가능성이 큽니다. 추론을 좀 더 연장하면, 만일 클래스에 소멸자가 선언되어 있으면, 복사 연산들을 자동으로 작성하지 않는 것이 바람직하다는 결론으로 이어집니다(자동으로 작성된 복사 연산들은 그 클래스에 적합하지 않을 것이므로). C++98 표준이 제정될 당시에는 이러한 추론이 충분한 공감대를 얻지 못했습니다. 그래서 C++98에서는 사용자 선언 소멸자의 존재가 컴파일러의 복사 연산 자동 작성 여부에 아무런 영향을 미치지 않았습니다. C++11에서도 여전히 그렇지만, 이는 단지 복사 연산들이 작성되는 조건들을 그런 식으로 제한하면 기존 코드가 너무 많이 깨질 것이라는 판단 때문입니다. 3의 법칙에 깔린 추론 자체는 여전히 유효하며, 복사 연산을 하나라도 선언하면 이동 연산들의 암묵적 작성이 배제된다는 점과 그 추론의 결합에 의해, 결과적으로 C++11은 사용자 선언 소멸자가 있는 클래스에 대해서는 이동 연산들을 작성하지 않습니다.
정리하자면, 클래스에 대한 이동 연산들은 다음 세 조건이 모두 만족될 때에만, 그리고 필요할 때에만, 자동으로 작성됩니다.
- 클래스에 그 어떤 복사 연산도 선언되어 있지 않음
- 클래스에 그 어떤 이동 연산도 선언되어 있지 않음
- 클래스에 소멸자가 선언되어 있지 않음
복사 연산들에도 이와 비슷한 규칙들을 어느 정도는 적용할 수 있습니다. C+=11에서는 복사 연산이나 소멸자를 선언하는 클래스에 대한 복사 연산들의 자동 작성이 비권장 기능으로 분류되었기 때문입니다. 즉, 만일 독자의 클래스 중에 소멸자나 복사 연산 중 하나를 선언하면 복사 연산들이 자동으로 작성된다는 점에 의존하는 것이 있다면, 그러한 의존성이 사라지도록 클래스를 업그레이드하는 것이 바람직합니다. 컴파일러가 작성한 함수들의 행동이 정확하다면(즉, 클래스의 비정적 자료 멤버들의 멤버별 복사가 바로 독자가 원하는 것이라면), 그러한 업그레이드는 쉬운 일입니다. C++11에서는 기본 행동을 사용하겠다는 의사를 "=default"를 이용해서 명시적으로 표현할 수 있기 때문입니다.
class Widget {
public:
...
~Widget(); // 사용자 선언 소멸자
... // 기본 복사 생성자
Widget(const Widget&) = default; // 기본 행동 OK
Widget& // 기본 복사 배정
operator=(const Widget&) = default; // 기본 행동 OK
...
};
'컴퓨터과학' 카테고리의 다른 글
[Effective C++] (17-4) 특수 멤버 함수들의 자동 작성 조건을 숙지할 것 / (똑똑한 포인터) (0) | 2020.06.13 |
---|---|
[Effective C++] (17-3) 특수 멤버 함수들의 자동 작성 조건을 숙지할 것 (0) | 2020.06.11 |
[Effective C++] (17-1) 특수 멤버 함수들의 자동 작성 조건을 숙지할 것 (0) | 2020.06.09 |
[Effective C++] (16-3) const 멤버 함수를 스레드에 안전하게 작성할 것 (0) | 2020.06.08 |
[Effective C++] (16-2) const 멤버 함수를 스레드에 안전하게 작성할 것 (0) | 2020.06.07 |