특수 멤버 함수는 사용자가 작성하지 않아도 컴파일러가 자동으로 작성하는 기본 생성자, 소멸자, 복사 생성자, 복사 대입 연산자를 가리킨다.

이 함수들은 명시적으로 작성되어 있지 않은 상태에서 해당 함수를 사용하는 코드가 존재할 때만 작성된다.

컴파일러에 의해 작성된 특수 멤버 함수는 기본적으로 public, inline이고 가상 소멸자가 존재하는 기반 클래스를 상속받아서 파생 클래스 역시 가상 소멸자가 생성되는 경우를 제외하고는 모두 비가상 함수이다.

 

class Widget {
public:
    Widget(Widget&& rhs); // 이동 생성자
    Widget& operator=(Widget&& rhs); // 이동 대입 연산자
};

C++11부터는 이동 생성자와 이동 대입 연산자까지 추가되어 총 6개의 특수 멤버 함수가 있다.

 

기본적으로 특수 멤버 함수는 사용자가 명시적으로 작성하지 않았지만 해당 함수를 사용하는 코드가 존재할때만 만들어진다고 했었다. 여기서 복사 연산과 이동 연산은 약간의 차이가 있다.

 

복사 생성자와 복사 대입 연산자는 서로 독립적이다. 복사 생성자가 명시적으로 선언되어 있고 복사 대입 연산자는 구현되어 있지 않은 상태에서 복사 대입 연산자를 사용한다면 컴파일러에 의해 복사 대입 연산자가 작성된다. 반대의 경우도 마찬가지이다.

 

하지만 이동 생성자와 이동 대입 연산자는 서로 종속적이다. 이동 생성자가 명시적으로 선언되어 있고 이동 대입 연산자는 구현되어 있지 않은 상태에서 이동 대입 연산자를 사용한다면 컴파일러는 복사 대입 연산자를 자동으로 작성하지 않는다. 반대의 경우도 마찬가지로 작성하지 않는다.

왜냐하면 하나라도 명시적으로 작성했다는 의미는 컴파일러가 기본적으로 작성해주는게 적합하지 않아서 사용자가 다른 방식으로 구현한 것이기 때문이다. 다른 한쪽을 컴파일러가 자동으로 작성해봤자 마찬가지로 적합하지 않을 가능성이 크기 때문에 한쪽이 명시적으로 선언되어 있다면 다른 한쪽은 자동으로 작성되지 않는다.

 

더 나아가서 복사 연산중 하나라도 명시적으로 선언되어있으면 이동 연산자는 자동으로 작성되지 않고 반대로 이동 연산자중 하나라도 명시적으로 선언되어있으면 복사 연산들은 삭제되어 비활성화된다.

 

3의 법칙

복사 생성자, 복사 대입 연산자, 소멸자 중 하나라도 선언했다면 나머지 둘도 선언해야 한다.

 

어떤 클래스의 복사 대입 연산을 직접 구현해야 하는 경우는 거의 항상 그 클래스가 자원 관리를 수행할 필요가 있기 때문이다. 구현한 복사 대입 연산이 수행하는 자원 관리 역시 다른 복사 연산에서도 수행해야 하고 소멸자도 그 자원의 관리에 참여해야 하기 때문에 셋 중 하나라도 선언했다면 나머지 둘도 선언해야 하는 것이다.

표준 라이브러리에서 메모리를 관리하는 모든 클래스가 2개의 복사 연산과 소멸자를 제공하는 이유가 이 때문이다.

사용자 선언 소멸자가 있는 클래스에서는 복사 연산들도 선언해야 하기 때문에 이동 연산들을 작성하지 않는다는 것을 추론할 수 있다.

 

정리하자면 다음과 같은 세 조건이 모두 만족할 때, 그리고 필요할 때에만 특수 멤버 함수가 자동으로 작성된다.

◾ 클래스에 그 어떤 복사 연산도 선언되어 있지 않다.

◾ 클래스에 그 어떤 이동 연산도 선언되어 있지 않다.

◾ 클래스에 소멸자가 선언되어 있지 않다.

 

class Widget {
public:
    ~Widget(); // 소멸자 선언. 복사 연산들도 선언해야 한다
    Widget(const Widget&) = default; // 기본 복사 생성자의 사용 의사를 밝힘
    Widget& operator(const Widget&) = default // 기본 복사 대입 연산자의 사용 의사를 밝힘
};

혹시라도 복사 연산, 소멸자 중 하나라도 작성한 상황에서 컴파일러가 자동으로 작성한 함수들의 행동이 정확하다면 C++11에서는 해당 함수들을 사용하겠다는 의사를 명시적으로 밝혀서 자동으로 작성된 코드를 사용할 수 있다.

 

만약 사용자 선언 소멸자를 두면서도 이동 연산을 지원하고 싶다면 이동 연산들에도 default를 지정하면 된다. 이 경우 복사 연산이 비활성화 되는데 복사 연산 역시 지원하고 싶다면 마찬가지로 default를 지정해준다.

 

class Base {
public:
    virtual ~Base() = default; // 가상 소멸자
    
    Base(Base&&) = default; // 이동 연산
    Base& operator=(Base&&) = default;
    
    Base(const Base&) = default; // 복사 연산
    Base& operator=(const Base&) = default;
};

 

특수 멤버 함수들을 관장하는 C++11의 규칙

기본 생성자

C++98의 규칙들과 같다. 클래스에 사용자 선언 생성자가 없는 경우에만 자동으로 작성된다.

 

소멸자

C++98의 규칙들과 같지만 기본적으로 noexcept라는 차이 하나만 존재한다.

기본적으로 작성되는 소멸자는 기반 클래스 소멸자가 가상일 때에만 가상이다.

 

복사 생성자

클래스에 사용자 선언 복사 생성자가 없을 때에만 자동으로 작성된다.

클래스에 이동 연산이 하나라도 존재하면 삭제된다.

 

복사 대입 연산자

클래스에 사용자 선언 복사 대입 연산자가 없을 떄에만 자동으로 작성된다.

클래스에 이동 연산이 하나라도 존재하면 삭제된다.

 

이동 생성자와 이동 대입 연산자

클래스에 사용자 선언 복사 연산들과 이동 연산들, 소멸자가 없을 때에만 자동으로 작성된다.

 

 

멤버 함수 템플릿이 존재한다고 해서 특수 멤버 함수의 자동 작성이 비활성화되지는 않는다.

 

◾ 컴파일러가 스스로 작성할 수 있는 멤버 함수들, 즉 기본 생성자와 소멸자, 복사 연산들, 이동 연산들을 가리켜 특수 멤버 함수라고 부른다.

◾ 이동 연산들은 이동 연산들이나 복사 연산들, 소멸자가 명시적으로 선언되어 있지 않은 클래스에 대해서만 자동으로 작성된다.

◾ 복사 생성자는 복사 생성자가 명시적으로 선언되어 있지 않은 클래스에 대해서만 자동으로 작성되며, 만일 이동 연산이 하나라도 선언되어 있으면 삭제된다. 소멸자가 명시적으로 선언된 클래스에서 복사 연산들이 자동 작성되는 기능은 비권장이다.

◾ 멤버 함수 템플릿 때문에 특수 멤버 함수의 자동 작성이 금지되는 경우는 전혀 없다.

+ Recent posts