Post List

2015년 4월 7일 화요일

item 17 : "Special" 멤버 함수 생성의 규칙

작가
Meyers, Scott
출판
O'ReillyMedia
발매
2014.12.12
평점








Effective Modern C++ - Scott Meyers
Item 17: Understand special member function generation

item 17. "Special" 멤버 함수 생성에 대해서 알아보자.

* "Special" member function 이란 무엇일까요 ?
  class 생성시 사용자가 만들어주지 않아도 컴파일러가 자동으로 만들어주는 멤버 함수를 뜻합니다.
  (이것이의 표준 용어인지 아닌지는 잘 모르겠습니다.

이하 설명에서 다음과 같이 약어를 사용하겠습니다.

- SMF : Special member function,
- MF : Member function
- ctor : Constructor
- dtor : Destructor
- AO : Assignment Operator (Ass라고 줄이고 싶었으나.. 쫌.... ;;;)
- OP : Operator = ctor + AO


* C++98의 SMF

- 기본생성자 (default ctor)
- 소멸자 (dtor)
- 복사 연산자 [ = 복사 생성자 + 복사 대입 연산자 ] ( COPY OP = COPY ctor + COPY AO )

이렇게 4개의 멤버 함수를 자동으로 만들어 줬습니다.

class Widget
{
public:
    Widget();                       // default ctor
    ~Widget();                      // dtor
    Widget(const Widget& SRC);      // COPY ctor
    Widget& operator=(Widget& SRC); // COPY AO
};

여기서 잠시만...
COPY ctor의 정의에 대해서 잠깐만 보고 넘어가겠습니다.

예를 들어서 class WidgetCOPY ctor 예제는 다음과 같습니다.

class Widget
{
public:
    Widget (const Widget& SRC);
    Widget (Widget& SRC);
    Widget (volatile Widget& SRC);
    Widget (const volatile Widget& SRC);
    Widget (Widget& SRC, int = 77);
    Widget (const Widget&, double = 0.7, int = 26);
    ...
}; // http://en.wikipedia.org/wiki/Copy_constructor 참조

COPY ctor의 특징은 다음과 같습니다.

1. 자신의 타입을 return 해야 합니다.
2. 첫번째 인자(argument)로 자신의 타입의 참조형(reference)를 받아야 하며, constvolatile 여부는 상관 없습니다.
3. 2번째 이상의 인자에 대해서는 default 값이 있어야 합니다.


SMF의 특징은 다음과 같습니다.

1. 각각에 대해서 선언이 없는 경우에만 자동으로 생성됩니다.
  (단 default ctor의 경우 다른 어떠한 ctor이라도 선언되어 있다면 생성되지 않습니다.)
2. 모두 publicinline 속성을 가집니다.
3. Base class의 dtor가 virtual인 경우, Derived class의 dtor 역시 virtual로 자동으로 생성됩니다.
  (dtor만 virtual을 붙여주지 나머지는 그렇지 않습니다.)


* C++11의 SMF

C++11에는 SMF에 2가지가 더 추가되었습니다.

- 이동 연산자 [ = 이동 생성자 + 이동 대입 연산자 ] (MOVE OP = MOVE ctor + MOVE AO )

이렇게 이동 연산자 2가지가 추가 되었습니다.

class Widget
{
public:
    Widget(Widget&& SRC);            // MOVE ctor
    Widget& operator=(Widget&& SRC); // MOVE AO
};

MOVE OP의 역할은 class 내의 non-static 멤버 변수를 MOVE하는데,
모든 타입이 MOVE 연산이 가능한건 아닙니다.
MOVE를 지원하지 않는 타입에 대해서는 COPY를 수행합니다.
만약 class 내의 모든 멤버변수가 MOVE 가능하고, Base classMOVE 가능하다면,
Derived class 역시 MOVE가 가능하게 됩니다.

MOVE OP는 COPY OP와 다른 점이 하나 있습니다.
MOVE OP 2가지 (MOVE ctor , MOVE AO) 는 서로 독립적이지 않습니다.
둘 중 하나가 선언이 되면, 나머지 하나도 자동으로 생성되지 않습니다.
(COPY OP의 경우 C++98, C++11 둘 다 서로 독립적입니다.)
왜냐면 MOVE ctor를 직접 작성한 경우, 멤버 변수 중 뭔가가 다른 법칙에 의해서 MOVE 되어야 한다고 판단하여,
MOVE AO에도 그 다른 법칙이 동일하게 처리되어야 할꺼라고 판단하여 자동으로 생성해주지 않습니다.


* Rule of Three ( in C++98 )

- Big Three ( dtor, COPY ctor, COPY AO) 중 하나를 선언 할 떄는 나머지도 다 같이 선언해 주어야 한다는 규칙입니다.

예를 들어서 class 내에 별도로 자원 관리 (Resource management)를 필요로 하는 멤버가 있는 경우,
( e.g. STL 컨테이너의 동적 메모리 할당 )

1. COPY ctor 에서 특별히 따로 처리를 해 주었다면, 당연히 COPY AO에서도 따로 처리를 해 주어야 합니다.
2. 이렇게 따로 처리를 해준 경우 dtor에서 해당 자원의 관리 (통상적으로 자원 해제) 를 해 주어야 합니다.

하지만 C++98에서는 모든 경우에 대해서 위와 같은 관계를 가지지 않을 수도 있다고 판단하여,
Big Three 를 서로 독립적으로 자동 생성되도록 하였습니다.
C++11에서도 과거 legacy code와의 호환성을 생각하여 Big Three에 대해서는 똑같이 유지하고 있습니다.


* Rule of Five ( in C++11 )

- Big Three 모두가 선언되지 않았을 경우 MOVE OP ( MOVE ctor, MOVE AO)가 자동으로 생성됩니다.

가 정확한 정의지만 Rule of Three와 비슷하게 기억하려면 다음과 같이 기억해도 무관할 것입니다.

- dtor, COPY ctor, COPY AO, MOVE ctor, MOVE AO 중 하나를 선언 할 때는 나머지도 같이 선언하세요.


* = default

만약 dtor를 명시적으로 선언하였으나, 자동으로 생성되는 COPY ctor, COPY AO가 정확하게 동작하는 거라면,
= default를 사용하여 컴파일러가 자동으로 생성해주게 할 수 있습니다.

class Widget
{
public:
    ~Widget();                            // user-declared dtor
    Widget(const Widget&) = default;      // default COPY ctor
    Widget& operator=(Widget&) = default; // default COPY AO
};

MOVE OP, 상속관계에서도 모두 사용이 가능합니다.
특히  Base class의 dtor를 virtual로 선언하고 싶을 때 다음과 같이 간단하게 처리가 가능합니다.

class Base
{
public:
    virtual ~Base() = default;         // virtual dtor

    Base(const Base&) = default;       // COPY ctor
    Base& operator=(Base&) = default;  // COPY AO

    Base(Base&&) = default;            // MOVE ctor
    Base& operator=(Base&&) = default; // MOVE AO
};

우리가 작업을 하다보면 디버깅 목적으로 Log를 여기저기 남겨 놓는 경우가 있습니다.
만약 디버깅 목적의 Code를 dtor에 삽입 할 경우,
당연히 MOVE OP, COPY OP가 자동으로 생성되지 않습니다.
위 OP 들을 사용했다던지, 해당 class의 개체를 STL의 컨테이너에 넣었다던지 등의 작업이 있었다면,
당연히 에러가 발생하겠죠 ?
이럴 때 default를 사용하여 컴파일러가 자동으로 생성해주게 하면 됩니다.

* C++11의 SMF 규칙 Full Version

- Default ctor : C++98과 같습니다. 다른 ctor가 없을때만 자동으로 생성됩니다.
- dtor : C++98과 같습니다. Base class가 virtual이면, Derived class도 virtual로 생성됩니다.
        기본적으로 noexcept로 생성됩니다. (C++98에는 noexcept라는 키워드 자체가 없었습니다.
- COPY OP (COPY ctor + COPY AO) : 동작은 C++98과 같습니다. non-static 멤버 변수를 복사해주는 역할을 합니다.
        MOVE OP가 생성되었을 땐 자동으로 생성되지 않습니다.
        다른 COPY OP 나 dtor와 같이 선언해주는게 좋습니다.
- MOVE OP (MOVE ctor + MOVE AO) : non-static 멤버 변수를 이동해주는 역할을 합니다.
        다른 MOVE OP, COPY OP, dtor가 선언되지 않았을 때만 자동으로 생성됩니다.


* template MF

template MF를 생성해도 다른 SMF의 자동생성에는 아무런 영향을 끼치지 않습니다.

class Widget
{
public:
    template<typename T>
    Widget(const T& SRC);

    template<typename T>
    Widget& operator=(const T& SRC);
};

위와 같이 COPY dtor, COPY AO 를 template로 선언을 하더라도 Widget에 대한 COPY dtor 와 COPY AO는 자동으로 생성됩니다.

Things to remember

* SMF (Special member function) 는 컴파일러가 자동으로 만들어주는 함수들입니다. : default ctor (constructor), dtor (destructor), COPY OP (operation = constructor + assignment operation), MOVE OP

* MOVE OP는 해당 함수에 MOVE OP, COPY OP, dtor가 선언되지 않았을 때만 자동으로 생성됩니다.

* COPY ctor는 COPY ctor이 명시적으로 선언되지 않았을 때 생성되고, MOVE OP가 선언되었을 때는 생성되지 않습니다.
  COPY AO는 COPY AO가 명시적으로 생성되지 않았을 때 생성되고, MOVE OP가 선언 되었을때을 때는 생성되지 않습니다.
  COPY OP와 dtor는 같이 선언해 주는게 좋습니다.

template MF (member function)는 SMF의 생성에 관여하지 않습니다.