ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Modern C++] 식별자 : override
    C++/Effective Modern C++ 2022. 4. 22. 17:38

    override 식별자는 C++11에서 추가된 식별자입니다.

    이 식별자는 const, virtual등의 기능적인 역할을 하는 것이 아닌, 알리는 (Notify)역할을 하는 식별자 입니다.

    특이한 점은, 기존에 기능, 가독성을 위해 사용한 기법들 (delete, static_cast<> 등)과는 다르게, 알리는 대상이 컴파일러 라는 것 입니다.

    이번 글에서는 override의 사용에 대해 살펴보도록 하겠습니다.

     


     

    1. 가상 함수 & 비 가상 함수

     

    더보기

    예제를 살펴보도록 하겠습니다.

    #include <iostream>
    using namespace std;
    
    class Anc {
    public:
        void func1() { std::cout << "Anc::func1\n"; }
        virtual void func2() { std::cout << "Anc::func2\n"; }
    };
    
    class Des : public Anc {
    public:
        void func1() { std::cout << "Des::func1\n"; }
        void func2() { std::cout << "Des::func2\n"; }
    };
    
    int main() {
        std::unique_ptr<Anc> a = std::make_unique<Des>();
        a->func1();
        a->func2();
    }
    Anc::func1
    Des::func2

    메소드 오버라이드 시 가상함수와 그렇지 않은 함수의 차이에 대한 예제입니다.

    기반 형식과 실제 형식에 따라 호출되는 함수가 다른 것을 볼 수 있습니다.

    자세한 내용은 다른 글 (가상 함수) 에 기술되어 있습니다.

     

    2. 가상 함수 오버라이드의 조건

     

    더보기

    위 문단의 코드를 아주 조금 수정해보도록 하겠습니다.

    #include <iostream>
    using namespace std;
    
    class Anc {
    public:
        void func1() { std::cout << "Anc::func1\n"; }
        virtual void func2() { std::cout << "Anc::func2\n"; }
    };
    
    class Des : public Anc {
    public:
        void func1() { std::cout << "Des::func1\n"; }
        void func2() const { std::cout << "Des::func2\n"; } // Here!
    };
    
    int main() {
        std::unique_ptr<Anc> a = std::make_unique<Des>();
        a->func1();
        a->func2();
    }
    Anc::func1
    Anc::func2

    수정된 라인에 주석으로 표시를 해 두었습니다.

    구체적으로 Des 클래스의 func2함수가 const가 되었습니다.

    이 사소한 변화가 꽤 큰 변화를 만들어냈는데, 출력을 보면 호출되는 함수가 바뀐 것을 볼 수 있습니다.

    Anc클래스의 func2가 가상함수임에도 불구하고, func2의 호출에 대해 기반 함수의 func2를 호출한 것을 볼 수 있습니다.

     

    가상 함수가 오버라이드 되려면 다음의 조건들을 만족해야 합니다.

    • 기반 클래스의 함수가 반드시 가상 함수이어야 합니다.
    • 기반 함수와 파생 함수의 이름이 반드시 동일해야 합니다. (소멸자는 예외)
    • 기반 함수와 파생 함수의 매개변수 형식들이 반드시 동일해야 합니다.
    • 기반 함수와 파생 함수의 상수성(const)이 반드시 동일해야 합니다.
    • 기반 함수와 파생 함수의 반환 형식이 반드시 동일해야 합니다.
    • 기반 함수와 파생 함수의 예외 명세가 반드시 호환되어야 합니다.
    • 기반 함수와 파생 함수의 참조 한정사가 반드시 동일해야 합니다.

    위의 조건들 중 만족하지 않는 것이 하나라도 있다면, 그것은 별개의 함수가 되어 오버라이드가 되지 않습니다.

     

    3. 식별자 : override

     

    더보기

    오버라이드가 제대로 이루어지지 않는다는 것은 설계상 치명적인 결함을 발생시킬 수 있습니다.

    하지만, 위 문단의 예제에서 본 것처럼 컴파일러는 일반적으로 이것을 문제삼지 않습니다.

     

    이런 문제를 해결해 줄 수 있는 키워드가 바로 override입니다.

    파생 함수에 override 선언을 하는 것으로, 컴파일러가 위 문단의 오버라이드 조건을 검사하도록 할 수 있습니다.

    #include <iostream>
    using namespace std;
    
    class Anc {
    public:
        void func1() { std::cout << "Anc::func1\n"; }
        virtual void func2() { std::cout << "Anc::func2\n"; }
    };
    
    class Des : public Anc {
    public:
        void func1() { std::cout << "Des::func1\n"; }
        void func2() const override { std::cout << "Des::func2\n"; } // Here!
    };
    
    int main() {
        std::unique_ptr<Anc> a = std::make_unique<Des>();
        a->func1();
        a->func2();
    }
    C3668 : 'Des::func2': 재정의 지정자 'override'가 있는 메서드가 기본 클래스 메서드를 재정의하지 않았습니다.

    위 문단의 Des::func2를 override로 선언할 경우 컴파일 에러가 발생합니다.

    컴파일러가 메소드 오버라이드에 대한 검사를 수행한다는 것은, 함수의 서명을 변경하는 데에도 도움이 됩니다.

    override가 적용된 함수의 서명을 변경을 고려해야 할 때, 컴파일러가 보고하는 오류 메시지들로 변경에 대한 오버헤드를 판단할 수 있습니다.

     


     

    override 식별자는 멤버 함수 선언의 끝에 있을 때에만 효력을 발휘합니다.

    예를 들어, 다음과 같은 선언은 유효하지 않습니다.

    #include <iostream>
    using namespace std;
    
    class Anc {
    public:
        virtual void func2() const { std::cout << "Anc::func2\n"; }
    };
    
    class Des : public Anc {
    public:
        void func2() override const { std::cout << "Des::func2\n"; } // Here!
    };

    이는 C++11에서 override 식별자가 나오기 이전의 코드를 고칠 필요가 없다는 것을 의미합니다.

    예약어로서 유효한 범위가 제한적이고, 개발 시의 실수를 줄여줄 수 있는 역할을 한다고 볼 수 있겠습니다.

    가상 함수를 사용할 때에는 override 식별자를 사용하는 것을 습관화 하는 것이 좋겠습니다.

     

    감사합니다.

    댓글

Designed by Tistory.