2 분 소요

  • 은근슬쩍 만들어 지는 것들은 유효한지 검토하고, 유효하지 않다면 사용하지 못하게 막아라.

모던 C++

개요

클래스의 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 정의하지 않으면, 이들을 사용할때 컴파일러가 암시적으로 정의합니다.(단, 기본 생성자는 아무 생성자라도 정의되어 있으면, 암시적으로 정의되지 않습니다. 암시적 기본 생성자를 참고하세요.)

항목 내용
T() {} 기본 생성자
T(const T& other) {} 복사 생성자
T& operator =(const T& other) {} 복사 대입 연산자
~T() {} 소멸자

다음 코드에서,

1
2
3
4
class T {
    int m_X;
    int m_Y;
};

기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자가 코드에서 사용되었다면, 클래스 T는 컴파일러에 의해 암시적으로 다음처럼 정의됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class T {
    int m_X;
    int m_Y;
public:
    // 기본 생성자
    T() {}

    // 복사 생성자 - 멤버별 복사 생성자 호출
    T(const T& other) :
        m_X(other.m_X),
        m_Y(other.m_Y) {}

    // 복사 대입 연산자 - 멤버별 복사 대입
    T& operator =(const T& other) {
        m_X = other.m_X;
        m_Y = other.m_Y;
    
        return *this;
    }
    // 소멸자 - 특별한 작업 없음
    ~T() {}
};

(C++11~) 이동 생성자, 이동 대입 연산자가 추가됨에 따라 기본 생성자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자, 소멸자의 암시적 정의가 재정립 되었습니다.

암시적 정의 수정 권장 방법

암시적 정의는 도움이 되기도 하지만, 타입 안전성이나 예외 보증에 독이 되기도 하므로, 반드시 유효한지 검토하고, 유효하지 않다면 사용 못하게 막거나 재구현 해야 합니다.

항목 문제점 권장 방법
기본 생성자 명시적 의존성 원칙에 따라 필요한 모든 요소를 나열하고 초기화 하는게 코딩 계약상 좋음 * 다른 생성자(값 생성자던, 복사 생성자) 정의
* private 또는 protected로 사용 제한
복사 생성자 포인터 멤버 변수의 소유권 분쟁이 발생함 * private 또는 protected로 사용 제한
* 멤버 변수로 스마트 포인터 사용(복사 생성자만 지원하는 스마트 포인터 참고, 스마트 포인터(shared_ptr 등) 참고)
복사 대입 연산자 포인터 멤버 변수의 소유권 분쟁이 발생함
* private 또는 protected로 사용 제한
* 멤버 변수가 1개인 경우 스마트 포인터 사용(복사 대입 연산자까지 지원하는 스마트 포인터 참고. 스마트 포인터(shared_ptr 등) 참고)
* 멤버 변수가 2개 이상인 경우 swap()으로 구현, 혹은 PImpl 이디엄으로 멤버 변수를 1개로 구현
소멸자 다형 소멸 주의 * 멤버 변수에 스마트 포인터 사용(복사 생성자만 지원하는 스마트 포인터 참고. 스마트 포인터(shared_ptr 등) 참고)
* 복사 생성이나 복사 대입 연산이 필요없다면 Holder 사용(Holder 참고)
* 상속할 수 없는 클래스 : public Non-Virtual 소멸자
* is-a관계로 상속하는 부모 클래스 : public Virtual 소멸자
* has-a관계로 상속하는 부모 클래스 : protected Non-Virtual 소멸자

(C++11~) 이동 생성자, 이동 대입 연산자가 추가됨에 따라 기본 생성자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자, 소멸자의 암시적 정의가 재정립 되었습니다.
(C++11~) default, delete가 추가되어 암시적으로 생성되는 멤버 함수의 사용 여부를 좀더 명시적으로 정의할 수 있습니다.

댓글남기기