재정의( overriding )
재정의는 기반( base ) 클래스의 동작을 파생( derived ) 클래스에서 다르게 동작하도록, 가상 함수를 다시 정의하는 장치( mechanism )를 말합니다.
이 장치를 이용하여, C++은 다형성( polymorphism)을 구현하고, 이러한 동작의 중심에는 가상 함수가 있습니다.
가상 함수에 관한 내용은 여기에서 볼 수 있습니다.
참고로, C++ 에는 중복 적재( overloading )라는 용어도 사용됩니다.
흔히 "오버로딩"이라고 불리는 이 단어는, 같은 종류의 기능을 하는 함수들을, 같은 함수 이름 사용을 통하여 통일된 호출이 가능하도록 만드는 함수 구분법이라고 할 수 있습니다.
예를 들어, 다음의 두 함수는 같은 이름을 갖고 있지만, 이 둘은 다른 함수로 구별( differentiation )이 가능합니다.
int add( int a, int b){
return a + b;
}
int add( double a, double b){
return a + b;
}
하지만, 다음의 두 함수는 구별할 수 없다는 라는 명세들을 담고 있는 규칙이라고 할 수 있겠습니다.
int add10( int a){
return a + 10;
}
int add10( const int& a){
return a + 10;
}
int main(){
int b = add10( 10);
}
▼출력
error: call of overloaded 'add10(int)' is ambiguous
이쯤 하고, 원래의 주제로 돌아와서,
재정의( overriding )가 동작하려면, 다음과 같은 조건들에 부합해야 합니다.
- 기반 클래스 함수가 반드시 가상 함수이어야 합니다.
- 기반 함수와 파생 함수의 이름이 반드시 동일해야 합니다.
- 기반 함수와 파생 함수의 매개변수 타입들이 반드시 동일해야 합니다.
- 기반 함수와 파생 함수의 const 속성이 반드시 동일해야 합니다.
- 기반 함수와 파생 함수의 반환 타입과 예외 명세가 반드시 호환되어야 합니다.
- 기반 함수와 파생 함수의 참조 한정사( reference qualifier )가 반드시 동일해야 합니다.
그런데, 실제로 코딩을 해보면, 위의 조건들을 무심코 어기는 경우들이 발생합니다.
( 혹은 위와 같은 조건이 있는지 모르는 경우가 있을 수도 있습니다. )
아래의 예들은 별이상 없어 보이지만, 재정의는 동작하지 않습니다.
그리고, 컴파일러도 이러한 불일치에 관해서 오류를 발생하지 않습니다.
class Base{
public:
virtual void func1(int n){}
virtual void func2( double d ) const {}
virtual void func3() & {
cout << "l-value func4 called\n";
}
int func4(){ return 0; }
};
// Base에서 상속받은 파생 클래스
class Derived : public Base{
public:
// 재정의 함수들
void func1(unsigned int n){}
void func2( double d){}
void func3() && {
cout << "r-value func4 called\n";
}
int func4(){ return 0; }
};
그래서, C++11에서 이러한 실수를 막기 위해 도입한 키워드가 override입니다.
override 키워드
override는 재정의하기 위해 작성한 가상 함수임을 컴파일러에게 알려주는 기능을 하는 키워드입니다.
이 키워드는 재정의할 함수의 맨 뒤에 위치하게 됩니다.
위의 예문에서 Derived 클래스의 func1 함수에 override 키워드를 사용하면 컴파일러는 오류를 발생합니다.
void func1(unsigned int n) override {}
▼출력
error: 'void Derived::func1(unsigned int)' marked 'override', but does not override
재정의한 func1 함수는 기반 함수와 다른 타입의 매개변수를 사용하기 때문입니다.
그리고, 재정의한 func2 함수는 const 속성이 일치하지 않습니다.
void func2( double d) override {} // error !
세 번째 함수인 func3는 참조 한정사( reference qualifier )가 일치하지 않습니다.
따라서, 다음과 같이 변경해야 합니다.
virtual void func3() & override {} // ok
마지막인 func4 함수는 Derived 클래스의 실수가 아니라, Base 클래스에서 실수가 있었습니다.
재정의 함수를 작성하려면, Base 클래스에서 func4 함수를 반드시 가상 함수로 선언해야 합니다.
파생 함수인 func4도 보통은 가상 함수로 선언하지만, 반드시 그렇게 해야 되는 것은 아닙니다.
그래서, 다음과 같이 변경해야 합니다.
class Base{
public:
// ...
virtual int func4(){ return 0; } // virtual 추가
};
class Derived : public Base{
public:
// ...
int func4() override { return 0; } // override 추가
};
이러한 실수들은 Derived 클래스에서 재정의 함수를 작성할 때, override 키워드를 사용하여 사전에 막을 수 있는 오류들입니다.
'C, C++ > C++ 언어' 카테고리의 다른 글
[C++] 범위 있는( scoped ) enum에 관하여 (0) | 2024.09.05 |
---|---|
[C++] 컴파일 시 사용 여부를 알려주는 constexpr 키워드 (2) | 2024.09.05 |
[C++] 초기화( initialization )의 종류 정리 (0) | 2024.08.31 |
[C++] 예외( exception ) 클래스 (0) | 2024.08.24 |
[C++] 생성자와 소멸자에서의 예외( exception ) 처리 (0) | 2024.08.24 |