KoreanFoodie's Study

Effective Modern C++ | 항목 12 : 재정의 함수들을 override 로 선언하라 본문

Tutorials/C++ : Advanced

Effective Modern C++ | 항목 12 : 재정의 함수들을 override 로 선언하라

GoldGiver 2022. 10. 26. 09:53

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

항목 12 : 재정의 함수들을 override 로 선언하라

핵심 :

1. 재정의 함수는 override 로 선언하라.
2. 멤버 함수 참조 한정사(reference qualifier)를 이용하면 멤버 함수가 호출되는 객체(*this)의 왼값 버전과 오른값 버전을 다른 방식으로 처리할 수 있다.


기반 클래스에서 정의된 가상 함수가 파생 클래스에서 재정의되기 위해서는 다음과 같은 조건들이 만족 되어야 한다.

  • 기반 클래스 함수가 가상함수 이어야 함
  • 기반 함수와 파생 함수의 이름이 동일해야 함(소멸자 제외)
  • 기반 함수와 파생 함수의 매개변수 형식들이 동일해야 함
  • 기반 함수와 파생 함수의 const 성이 동일해야 함
  • 기반 함수와 파생 함수의 반환 형식과 예외 명세(exception specification) 가 반드시 호환되어야 함

C++11 에는 다음과 같은 조건 하나가 추가되었다.

  • 멤버 함수들의 참조 한정사(reference qualifier) 들이 동일해야 함

멤버 함수 참조 한정사는 멤버 함수를 왼값에만 또는 오른값에만 사용할 수 있게 제한하는 기능이다. 비가상 함수에도 멤버 함수 참조 한정사를 적용할 수 있다.

class Widget {
public:
  // 이 버전은 *this 가 왼값일 때에만 적용
  void doWork() &;

  // 이 버전은 *this 가 오른값일 때에만 적용
  void doWork() &&;
};

...

// 팩터리 함수 (오른값 반환)
Widget makeWidget();

// 왼값을 돌려줌
Widget w;

...

// Widget::doWork() & 호출 (왼값 버전)
w.doWork();

// Widget::doWork() && 호출 (오른값 버전)
makeWidget().doWork();

기반 클래스의 가상 함수에 참조 한정사가 있으면, 해당 함수를 오버라이딩 하기 위해서는 파생 클래스의 가상 함수에도 정확히 같은 참조 한정사가 붙어 있어야 한다. 그렇지 않으면 오버라이딩이 아니라 숨김(hide) 이 일어난다. 아래 코드를 보며 문제점을 다시 확인해 보자.

class Base {
public:
  virtual void mf1() const;
  virtual void mf2(int x);
  virtual void mf3() &;
  void mf4() const;
};

class Derived {
public:
  virtual void mf1();
  virtual void mf2(unsigned int x);
  virtual void mf3() &&;
  void mf4() const;
};
  • mf1 : const 가 없음
  • mf2 : 매개변수 타입이 다름
  • mf3 : 참조 한정사가 다름
  • mf4 : 기반 클래스에서 가상 함수로 선언되지 않음

위의 함수들의 끝에 override 를 붙여주면, 컴파일러가 재정의 관련 문제점들을 지적해 준다. 위 코드를 수정하면 다음과 같다.

class Base {
public:
  virtual void mf1() const;
  virtual void mf2(int x);
  virtual void mf3() &;
  virtual void mf4() const;
};

class Derived {
public:
  virtual void mf1() const override;
  virtual void mf2(int x) override;
  virtual void mf3() & override;
  void mf4() const override; // virtual 은 붙여도, 안 붙여도 됨
};

override 는 키워드로, 함수 마지막에 붙었을 때만 예약된 의미를 가지므로, void override(); 같은 코드가 나와도 너무 당황하지는 말자.
이제 참조 한정사를 사용하는 실 사용 예시를 보자.

class Widget {
public:
  using DataType = std::vector<double>;
  DataType& data() & { return values; }
  DataType&& data() && { return values; }

private:
  DataType values;
};

// 팩터리 함수
Widget makeWidget();

...

Widget w;

// Widget::data 의 왼값 버전 호출
// vals1 은 복사 생성됨
auto vals1 = w.data();

// Widget::data 의 오른값 버전 호출
// vals2 은 이동 생성됨
auto vals2 = makeWidget().data();

만약 위의 코드에서, data( ) 함수에 참조 한정사가 붙지 않고 참조 타입만 리턴하는 경우, val2 의 경우 이동 생성이 아니라 복사 생성이 일어난다. 왜냐하면 data( ) 는 왼값 참조를 돌려주기 때문이다! 하지만 오른값을 전달할 경우, 이동 생성이 가능하게 되는 구현을 원하므로, 참조 한정사를 붙임으로써 이러한 문제를 해결했다.

Comments