Effective C++/Effective Modern C++
[Effective Modern C++] 12. override 키워드와 참조 한정사(reference qualifier)
김디트
2025. 2. 20. 10:33
728x90
항목 12. 재정의 함수들을 override로 선언하라
가상 함수 재정의
- 파생 클래스 함수를 기반 클래스의 인터페이스를 통해 호출할 수 있게 만드는 매커니즘
class Base
{
public:
virtual void doWork();
...
};
class Derived : public Base
{
public:
virtual void doWork();
...
};
std::unique_ptr<Base> upb = std::make_unique<Derived>();
...
upb->doWork(); // 기본 클래스의 인터페이스로 파생 클래스의 함수가 호출된다.
- 재정의를 위한 필수 조건
- 기본 클래스 함수가 반드시 가상함수여야 한다.
- 함수 이름이 반드시 동일해야 한다.(소멸제는 예외)
- 함수의 매개변수 타입이 반드시 동일해야 한다.
- 함수의 const 성이 반드시 동일해야 한다.
- 함수의 리턴 형식과 예외 명세(/exception specification)가 반드시 호환되어야 한다.
- C++11에 추가된 조건
- 함수의 참조 한정사(reference qualifier)가 반드시 동일해야 한다.
멤버 함수 참조 한정사(reference qualifier)
- 멤버 함수를 lvalue 혹은 rvalue에서만 사용할 수 있게 제한할 수 있다.
class Widget
{
public:
...
void doWork() &; // *this가 lvalue일 때만 적용된다.
void doWork() &&; // *this가 rvalue일 때만 적용된다.
};
Widget makeWidget(); // rvalue 리턴
Widget w; // lvalue
w.doWork(); // Widget::doWork & 버전 호출
makeWidget().doWork(); // Widget::doWork && 버전 호출
- 즉, const와 비슷한 용법으로 사용된다.
- lvalue에서만 사용해야 안전한 상황이 있을 수 있다.
class Widget
{
public:
using DataType = std::vector<double>;
...
DataType& data() { return values; }
...
private:
DataType values;
};
Widget w;
Widget makeWidget();
...
auto vals1 = w.data(); // std::vector<double>&가 적재된다.
auto vals2 = makeWidget().data(); // 마찬가지. 하지만 임시 객체의 참조이다!
- 참조 한정사를 사용하면 안전하게 사용할 수 있다.
class Widget
{
public:
using DataType = std::vector<double>;
...
DataType& data() & { return values; }
DataType& data() && { return std::move(values); }
...
private:
DataType values;
};
Widget w;
Widget makeWidget();
...
auto vals1 = w.data(); // & 버전이 호출된다.
auto vals2 = makeWidget().data(); // && 버전이 호출된다.
- overload 시에 조심할 것.
- 참조 한정사 버전이 있는데, 참조 한정사가 없는 버전을 오버로드하면 중의적인 호출이 될 수 있다.
재정의 실수 상황
- 의도와 다르게 행동해도 컴파일은 대개 성공한다.
- 컴파일러에 따라 경고가 발생할 수도 있고 아닐 수도 있다.
class Base
{
public:
virtual void mf1() const;
virtual void mf2(int x);
virtual void mf3() &;
void mf4() const;
};
// 실수하기 쉬운 상황들
class Derived : public Base
{
public:
virtual void mf1(); // const성이 달라서 재정의 실패
virtual void mf2(unsigned int x); // 매개변수 타입이 달라서 재정의 실패
virtual void mf3() &&; // 참조 한정사가 달라서 재정의 실패
void mf4() const; // virtual이 붙지 않아서 재정의 실패
};
- 실수하기 너무 쉽기 때문에 override 키워드를 붙인다.
class Base
{
public:
virtual void mf1() const;
virtual void mf2(int x);
virtual void mf3() &;
virtual void mf4() const;
};
// 이제 의도대로 재정의한다.
class Derived : public Base
{
public:
virtual void mf1() const override;
virtual void mf2(int x) override;
virtual void mf3() & override;
void mf4() const override; // virtual을 붙이는 건 선택사항
};
리팩토링 시의 용이함
- override 키워드가 붙었을 때 기본 클래스의 함수 사양을 바꾸면 컴파일러는 경고해준다.
- 하지만, 그렇지 않다면 일일이 어떻게 찾아낼 것인가...
C++11의 컨텍스트 키워드(contextual keyword)
- C++11에서 final과 override가 추가되었다.
- 하지만, 함수 선언의 끝에 나올때만 의미를 가지므로 아래와 같은 상황은 굳이 처리해줄 필요 없다.
class Warning
{
public:
...
void override(); // override 키워드로 동작할 리 없으므로 놔둬도 무방
...
};
728x90