-
[Effective Modern C++] 식별자 : overrideC++/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 식별자를 사용하는 것을 습관화 하는 것이 좋겠습니다.
감사합니다.
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] noexcept (0) 2022.04.28 [Effective Modern C++] const_iterator (0) 2022.04.27 [Effective Modern C++] private와 delete (0) 2022.04.22 [Effective Modern C++] enum과 enum 클래스 (0) 2022.04.18 [Effective Modern C++] using과 typedef (0) 2022.04.05