Post List

2015년 4월 1일 수요일

item 12: 오버라이딩 함수엔 override 라고 쓰자.

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








Effective Modern C++ - Scott Meyers
Item 12: Declare overriding functions override.

Item 12. 오버라이딩 함수엔 override 라고 쓰자.

C++에서 함수 오버로딩(overloading)멤버 함수 오버라이딩(overriding) 이 비슷한거라고 생각하는 사람이 이외로 좀 됩니다.

Overloading은 같은 함수 이름에 인자(Argument)가 다른... 대충 뭔지 아시죠 ?
(매우 불친절 모드... 여기서 이 얘기를 할껀 아니라서요. ㅋ)

Overriding은 전혀 다름 개념입니다.
C++98에서 overriding을 하기 위해서는 5가지 조건이 만족해야 합니다.
(참고로 설명에서 ' Base class와 Derived class의 함수 ' 는 생략하겠습니다.)

1. Base class에서 virtual로 선언되어 있어야 함.
2. 이름이 같아야 함. (dtor 는 예외)
3. 인자 목록, 타입이 같아야 함.
4. const속성이 같아야 함.
5. 리턴 타입과 예외 명세가 호환되어야 함.

C++11에서는 한가지가 더 추가되었습니다.

6. 참조 속성(reference qualifier)이 같아야 함.

함수의 참조 속성을 처음 들어보시는 분도 계실껍니다.
(일단 자진해서... 저도 첨듣습니다. ㅎㅎ)
특정 멤버 함수를 L-Value 또는 R-Value 전용으로 만드는 기능입니다.

간단한 예제를 하나 보시면 쉽게 이해가 될꺼에요.

class Widget {
public:
    void Run() &;  // *this L-Value 경우
    void Run() &&; // *this R-Value 경우
};

Widget MakeWidget(); // Factory Method : return R-Value
Widget W;            // Widget Object (L-Value)

void main()
{
    W.Run();
    MakeWidget().Run();
}

Widget MakeWidget()
{
    Widget temp;
    return temp;
} 
void Widget::Run() &
{
    std::cout << "L-VALUE" << std::endl;
} 
void Widget::Run() &&
{
    std::cout << "R-VALUE" << std::endl;
}

실행을 하면 L-Value 인 경우와 R-Value 인 경우가 각각 다른 함수가 호출되는 것을 확인할 수 있습니다.
이것을 이용하여 L-Value 인 경우는 값을 COPY하고 R-Value인 경우는 MOVE하는 멤버 함수를 만드는 것은 어렵지 않겠죠 ?

Widget & Get() &  { return *this; }
Widget   Get() && { return std::move(*this); }

잠시 얘기가 다른 곳으로 빠졌지만, 중요한 내용이니 알아두면 좋습니다.
다시 원래 내용으로 돌아가죠. ㅎㅎ

기존 방식의 오버라이딩의 가장 큰 문제점은
제대로 오버라이딩 되지 않더라도, 컴파일러가 아무런 경고나 오류를 발생시키지 않고 정상적으로 컴파일 된다는 점입니다.
(몇몇 컴파일러는 경고를 주기도 하지만, 모든 컴파일러가 다 그렇지는 않습니다.)
그래서 실제로는 오버라이딩 되지 않은 함수들을 Base classDerived class에서 따로 호출하면서 원하는대로 동작하지 않게 되는 경우가 많습니다.

다음 예제에서 제대로 오버라이딩 된 함수는 몇 개일까요 ? 그리고 어떤 함수일까요 ?

class Base {
public:
    virtual void F1() const;
    virtual void F2(int x);
    virtual void F3() &;
    void F4() const;
};

class Derived : public Base {
public:
    virtual void F1();
    virtual void F2(unsigned int x);
    virtual void F3() && ;
    void F4() const;
};

몇 개 찾으셨나요 ??
전 하나도 못찾았어요. 정답은 하나도 없습니다.

F1 : const 속성이 다릅니다.
F2 : 인자가 다릅니다. int <-> unsigned int
F3 : 참조 속성이 다릅니다. & <-> &&
F4 : Base에서 virtual로 선언하지 않았습니다.

위 Code에서 Derived class의 모든 함수 뒤에 override를 붙이면 어떻게 될까요 ?

class Derived : public Base {
public:
    virtual void F1() override;
    virtual void F2(unsigned int x) override;
    virtual void F3() && override;
    void F4() const override;
};

모두 다 오류가 납니다.
override라는 키워드는 컴파일러에게 이 함수는 오버라이딩 함수라는 것을 알려줍니다.
컴파일러는 오버라이딩 가능한지를 판단해서 안되면 오류를 발생시킵니다.
이제 개발자의 의도를 확실히 컴파일러에게 전달이 가능해졌습니다.

이제 컴파일 오류가 일어나지 않도록 고쳐진 Code는 다음과 같습니다.

class Base {
public:
    virtual void F1() const;
    virtual void F2(int x);
    virtual void F3() &;
    virtual void F4() const;
};

class Derived : public Base {
public:
    virtual void F1() const override;
    virtual void F2(int x) override;
    virtual void F3() & override;
    void F4() const override;
};

F1 ~ F3은 Base class의 함수와 명세를 맞추는 작업을 했으며, F4의 경우는 Base class에 virtual를 붙였습니다.
virtual의 경우 오버라이딩하는 Derived class 의 함수에는 붙이지 않아도 됩니다.

여기서 override에 대한 설명은 마치고, 다른 얘기를 잠깐 하겠습니다.

C++11에 상황적 키워드 (Contextual keyword) 라는게 생겼습니다. (정확한 한글 표현이 아직 없는듯합니다.)
해당 키워드는 특정한 상황에만 키워드로 동작하며, 그 이외에 상황에서는 일반적으로 (변수이름, 함수이름..) 사용이 가능한 것을 말합니다.

대표적인 예로는 override final이 있습니다.
해당 키워드는 각 문장의 마지막에 있을 경우에만 키워드로 해석됩니다.
(참고로 final은 더이상 해당 함수를 override 할 수 없음을 나타내는 키워드 입니다.)

이렇게 상황적 키워드라는 것을 만든 이유는 아마도,
이미 기존에 override final을 일반적으로 사용한 Code에서 컴파일 에러를 내지 않게 하기 위해서인 것으로 판단됩니다.

class final {
public:
    void override();
};

이런 Code도 컴파일 오류없이 잘 동작합니다.

Things to Remember

* 오버라이딩 함수엔 override 라고 쓰면 컴파일러가 제대로 됬는지 검사해 줍니다.

* 멤버 함수 참조 규정(Member function reference qualifier) 을 활용하면
   L-Value 인 경우와 R-Value인 경우를 다르게 처리되게 만들 수 있습니다.