C++의 공식 어법에서는 특수 멤버 함수(special member function)이 있다. 세세한 조건들이 필요하긴 하지만 C++98에서는 기본 생성자, 소멸자, 복사 생성자, 복사 배정 연산자가 있다. 이 함수들은 꼭 필요한 경우에만, 즉 이 함수들이 클래스에 명시적으로 선언되어 있지는 않지만 이 함수들을 사용하는 클라이언트 코드가 존재할 때에만 작성된다. 기본 생성자는 클래스에 생성자가 하나도 선언되어 있지 않을 때 작성된다. 작성된 특수 멤버 함수들은 암묵적으로 public 이며 inline이며, 가상 소멸자가 있는 base class를 상속하는 파생 클래스의 소멸자를 제외하고는 nonvirtual이다. 가상 소멸자가 있는 base class를 상속하는 경우 파생 클래스의 소멸자는 virtual로 선언된다.
C++11에서는 두 멤버 함수를 추가했다. 이동 생성자(move constructor)와 이동 배정 연산자(move assignment operator)이다.
class Widget {
public:
...
Widget(Widget&& rhs); //이동 연산자
Widget& operator=(Widget&& rhs); // 이동 배정 연산자
...
};
기본적으로 이동 생성자는 주어진 매개변수 rhs의 비정적 자료멤버 각각을 이용해 클래스의 해당 자료 멤버들을 각각 이동 생성(move-construct)하고 이동 배정 연산자는 주어진 매개변수 rhs의 비정적 자료멤버 각각을 클래스의 해당 자료 멤버들에 각각 이동 배정(move-assign)한다.
복사 연산들과 마찬가지로 클래스 작성자가 명시적으로 작성한 이동 연산자들은 자동으로 작성되지 않는다.
복사 연산의 경우 복사 생성자와 복사 배정 연산자는 독립적이기 때문에 하나만 선언했다 해도 컴파일러가 자동으로 선언해 준다.
이동 연산의 경우 독립적이지 않기 때문에 둘 중 하나를 선언하면 컴파일러는 다른 하나를 선언하지 않는다. 만약 클래스 작성자가 이동 연산을 명시적으로 선언했다면 기본적인 이동 연산이 적합하지 않기 때문일 것이다. 이 경우 다른 이동 연산 또한 적합하지 않을 수 있기 때문에 컴파일러는 자동으로 작성하지 않는다. 복사연산도 마찬가지이다. 복사연산을 명시적으로 선언한 경우 일반적인 복사 방법과 다를 것이므로 이동 또한 적합하지 않을 가능성이 크기 때문에 컴파일러는 이동 연산을 자동으로 작성하지 않는다. 반대도 마찬가지이다. 이동 연산을 명시적으로 선언하면 컴파일러는 복사연산들을 delete 해버린다.
3의 법칙
만일 복사 생성자와 복사 배정 연산자, 소멸자 중 하나라도 선언했다면 나머지도 전부 선언해야 한다는 것이다.
이 지침이 필요한 클래스의 경우 거의 항상 자원 관리를 수행한다. 그런 클래스에서는 한 복사 연산이 수행하는 자원 관리를 다른 복사연산에서도 수행해야 하며, 클래스의 소멸자 역시 그 자원의 관리에 참여한다. 3의 법칙에 의하면 명시적으로 선언한 소멸자가 있다는 것은 자동으로 작성된 복사 연산이 맞지 않을 수 있으므로 복사 연산들을 자동으로 생성하지 않는다.
정리하면 세 조건이 모두 만족될 때에만, 필요할 때에만 자동으로 작성된다.
- 클래스에 그 어떤 복사 연산도 선언되어 있지 않다.
- 클래스에 그 어떤 이동 연산도 선언되어 있지 않다.
- 클래스에 소멸자가 선언되어 있지 않다.
C++11에서는 복사 연산이나 소멸자를 선언하는 클래스에 대한 복사 연산들이 자동 작성이 비권장 기능으로 분류되었다. 하지만 컴파일러가 작성한 함수들의 행동이 정확하다면 = default를 사용해 명시적으로 기본 행동을 사용하겠다고 표현할 수 있다.
class Widget{
public:
...
~Widget();
...
Widget(const Widget&) = default; // 기본 복사 생성자 사용
Widget& operator=(const Widget&) = default; // 기본 복사 배정 사용
...
};
하나의 이동/복사 연산을 명시적으로 작성한 경우 각 연산들에 =default를 사용함으로서 기존 연산들을 계속 사용 할 수 있다.
특수 멤버를 관장하는 C++11의 규칙들이다.
- 기본 생성자 : C++98의 규칙들과 같다. 클래스의 명시적 선언 생성자가 없는 경우 자동 생성된다.
- 소멸자 : C++98과 같다. 유일한 차이는 소멸자가 기본적으로 noexcept인 점이다.
- 복사 생성자 : 실행 시점 행동은 C++98과 같다. 비 정적 자료 멤버들을 멤버별로 복사 생성한다. 명시적으로 생성되지 않았을 때만 자동 생성되며 명시적으로 생성된 복사 배정 연산자나 소멸자가 있는 클래스에서는 자동 작성되는 기능이 비권장된다.
- 복사 배정 연산자 : 실행 시점 행동은 C++98과 같다. 비 정적 자료 멤버들을 멤버별로 복사 배정하며, 명시적으로 생성되지 않았을 때만 자동 생성, 명시적으로 생성된 복사 생성자나 소멸자가 있는 클래스에는 자동 작성이 비권장된다.
- 이동 생성자와 이동 배정 연산자 : 비정적 자료 멤버의 멤버별 이동 수행한다. 명시적으로 선언된 복사 연산과 이동 연산, 소멸자가 없을때 자동으로 생성된다.
Conclusion
- 컴파일러가 스스로 작성할 수 있는 멤버 함수들, 즉 기본 생성자, 소멸자, 복사 연산들, 이동 연산들을 가리켜 특수 멤버 함수라고 부른다.
- 이동 연산들은 이동 연산들이나 복사 연선들, 소멸자가 명시적으로 선언되어 있지 않은 클래스에 대해서만 자동으로 작성된다.
- 복사 생성자는 복사 생성자가 명시적으로 선언되어 있지 않은 클래스에 대해서만 자동 작성되며, 만일 이동 연산이 하나라도 선언되어 있으면 삭제된다. 복사 배정 연산자는 복사 배정 연산자가 명시적으로 선언되어 있지 않은 클래스에 대해서만 자동 작성되며, 만일 이동 연산이 하나라도 선언되어 있으면 삭제된다. 소멸자가 명시적으로 선언된 클래스에서 복사 연산들이 자동 작성되는 기능은 비권장이다.
- 멤버 함수 템플릿 때문에 특수 멤버 함수의 자동 작성이 금지되는 경우는 전혀 없다.
Reference
Effective Modern C++ 항목 17 : 특수 멤버 함수들의 자동 작성 조건을 숙지하라
'C++ > Effective Modern C++' 카테고리의 다른 글
소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라 (0) | 2020.07.13 |
---|---|
std::unique_ptr (0) | 2020.07.12 |
const 멤버 함수를 스레드에 안전하게 작성하라 (0) | 2020.07.09 |
가능하면 항상 constexpr을 사용하라 (0) | 2020.07.08 |
예외를 방출하지 않을 함수는 noexpect로 (0) | 2020.07.07 |
댓글