스토리텔링 개발자

[Effective Modern C++] 17. 특수 멤버 함수(special member function) 본문

Effective C++/Effective Modern C++

[Effective Modern C++] 17. 특수 멤버 함수(special member function)

김디트 2025. 2. 27. 11:33
728x90

항목 17. 특수 멤버 함수들의 자동 작성 조건을 숙지하라

 

 

 

특수 멤버 함수(special member function)
  • C++이 자동으로 작성하는 멤버 함수
  • 꼭 필요한 경우에만 자동으로 작성된다.
    • 인수를 받는 생성자만 명시적으로 선언된 클래스엔 기본 생성자가 작성되지 않는다.
  • 작성된 특수 멤버 함수들은 암묵적으로 inline public nonvirtual이다.
    • 가상 소멸자가 있는 기본 클래스를 상속받으면 소멸자는 가상으로 선언된다.
  • C++98에서는...
    • 기본 생성자, 소멸자
    • 복사 생성자
    • 복사 할당 연산자
  • C++11에서는 두 가지가 추가되었다.
    • 이동 생성자(move constructor)
    • 이동 할당 연산자(move assignment operator)
class Widget
{
public:
    ...
    Widget(Widget&& rhs); // 이동 생성자
    
    Widget& operator=(Widget&& rhs); // 이동 할당 연산자
    ...
};

 

 

 

새로 추가된 함수들로 이동 연산 시
  • 반드시 이동 연산이 일어난다는 보장은 없다.
  • 사실 이동 '요청'에 더 가깝다.
  • 이동을 적용할 수 없는 타입은 복사 연산으로 동작하게 된다.
  • 결국 멤버별 이동은 이동할 원본 객체에 std::move를 적용하는 것에 불과하다.(항목 23 참조)

 

 

 

C++11 자동 작성 규칙
  • 복사 연산과 마찬가지로, 명시적으로 선언한 이동 연산들은 자동으로 작성되지 않는다.
  • 하지만, 복사 연산의 경우 각각(복사 생성자, 복사 할당 연산자)은 독립적으로 동작한다.
    • 복사 생성자만 선언되어 있을 시,
      • 복사 할당 연산자를 사용하면, 복사 할당 연산자는 컴파일러가 자동으로 작성한다.
      • 즉, 컴파일 에러는 발생하지 않는다.
  • 이동 연산의 경우 독립적이지 않다.
    • 이동 연산이 하나라도 커스텀 생성되어 있으면 이동 연산 관련은 자동 작성되지 않는다. 
    • 이동 생성자만 선언되어 있을 시,
      • 이동 할당 연산자를 사용하면, 이동 할당 연산자를 컴파일러가 자동 작성하지 않는다.
      • 즉, 컴파일 에러가 발생한다.
    • 그 이유는?
      • 커스텀 이동 연산을 만든 이유는 아마 기본 이동 로직이 그 클래스에 적합하지 않기 때문일 것이다.
      • 그럼 자동으로 만든 함수가 의미가 없을 가능성이 크다.
  • 복사 연산이 하나라도 커스텀 작성되어 있으면, 이동 연산들은 자동 작성되지 않는다.
    • 그 이유는?
      • 복사 연산을 선언했다는 것은, 일반 객체 복사 방식이 그 클래스에 적합하지 않기 때문일 것이다.
      • 복사가 적합하지 않다면 이동 연산도 적합하지 않을 가능성이 크다.
  • 이동 연산이 하나라도 커스텀 작성되어 있으면, 복사 연산들은 자동 작성되지 않는다.
    • 위와 마찬가지의 이유이다.
    • C++98과의 호환성은?
      • C++98에는 어차피 이동 연산이란 것이 없으므로 상충되지 않는다.
  • 소멸자가 선언되어 있으면, 이동 연산들은 자동 작성되지 않는다.
    • 복사 연산에 적용할 수는 없다. C++98과의 호환성 문제.
  • 즉, 이동 연산들은 아래 세 조건이 모두 만족하고 꼭 필요한 경우, 자동으로 작성된다.
    • 클래스에 그 어떤 복사 연산도 선언되어 있지 않다.
    • 클래스에 그 어떤 이동 연산도 선언되어 있지 않다.
    • 클래스에 소멸자가 선언되어 있지 않다.
  • 특수 상황) 멤버 함수 템플릿이 있어도 이동 연산 자동 작성은 된다.
class Widget
{
    ...
    template<typename T>
    Widget(const T& rhs);
    
    template<typename T>
    Widget& operator=(const T& rhs);
    ...
};

// 만약 T가 Widget이면 기본 복사 생성자와 동일한 모양이다.
// 그러나, 그렇다고 하더라도 이동 연산들은 컴파일러가 자동 작성 해준다.

 

 

 

3의 법칙(Rule of Three)
  • 복사 생성자, 복사 할당 연산자, 소멸자 중 하나라도 선언했으면 나머지도 모두 선언해야 한다는 규칙.
  • 그 이유는?
    • 복사 할당 연산을 커스텀한 이유는, 자원 관리를 직접 해야 한다는 뜻일 것이다.
    • 복사 시, 소멸 시엔 관리하고 있는 메모리를 처리할 의무가 있다.
  • C++98이 처음 제정될때는 위의 추론이 충분한 공감대를 얻지 못했기 때문에 독립적으로 자동 생성되게 되었다.

 

 

 

C++98의 복사 연산 코드를 C++11 제정에 따르도록 업그레이드 하기
  • 복사 연산의 자동 생성에 의존하는 코드가 있다면 default 키워드를 사용하여 업그레이드 해주자.
class Widget
{
public:
    ...
    ~Widget(); // 커스텀 소멸자가 있다.
    // 이동 연산과 같은 룰을 적용하면, 복사 연산은 자동 생성되면 안된다.
    // 그럼에도 복사 생성이 필요하고, 기본 동작으로 동작해야 한다면...
    ...
    
    // 컴파일러가 자동 생성하던 기본 복사 생성자를 명시적 선언
    Widget(const Widget&) = default;
    // 컴파일러가 자동 생성하던 기본 복사 할당 연산자를 명시적 선언
    Widget& operator=(const Widget&) = default;
    ...  
};

 

 

 

default의 추가 용도
  • 다형성 클래스를 작성하며 소멸자를 virtual로 하고 싶다.
    • 근데 소멸자 기능 자체는 기본의 것을 사용해야 한다면?
  • 그리고 이렇게 소멸자를 직접 선언하면, 이동 연산의 자동 작성이 불가능해진다.
    • 커스텀 소멸자를 두면서 기본 이동을 지원해야 한다면?
class Base
{
public:
    // 가상 소멸자
    virtual ~Base() = default;
    
    // 이동 지원
    Base(Base&&) = default;
    Base& operator=(Base&&) = default;
  
    // 복사 지원
    Base(const Base&) = default;
    Base& operator=(const Base&) = default
    ...
};

 

 

 

컴파일러가 자동생성 해줘도 기본으로 default 키워드 적용하기
  • 이렇게 하는 게 의도가 더 명확하기도 할 뿐더러
  • 미묘한 버그를 피할 수 있다.
// 아래 클래스는 복사 연산, 이동 연산, 소멸자가 없으므로 자동 생성될 것이다.
class StringTable
{
public:
    StringTable() {}
    ...
private:
    std::map<int, std::string> values;
};

// 근데 아래처럼 기본 생성과 소멸 기능을 추가했다면?
class StringTable
{
public:
    StringTable()
    { makeLogEntry("Creating StringTable object"); } // 추가
    ~StringTable()
    { makeLogEntry("Destroying StringTable object"); } // 추가
    ...
private:
    std::map<int, std::string> values;
};
// 이제 이동 연산은 자동 작성되지 않는다.
// 허나, 복사 연산은 자동 작성된다.
// 이동 연산으로 처리되던 것들이 이젠 복사 연산으로 처리되게 된다!!
// 컴파일 에러조차 없다..

 

728x90
Comments