함수 오버라이딩(Function overriding)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include <iostream> using namespace std; class First { public: void MyFunc() { cout<<"FirstFunc"<<endl; } }; class Second : public First { void MyFunc() { cout<<"SecondFunc"<<endl; } }; class Third : public Second { void MyFunc() { cout<<"ThirdFunc"<<endl; } }; int main(void) { Third * tptr = new Third(); Second * sptr = tptr; First * fptr = sptr; fptr->MyFunc(); sptr->MyFunc(); tptr->MyFunc(); delete tptr; return 0; } | cs |
[출력결과]
FirstFunc
SecondFunc
ThirdFunc
위 예제의 'MyFunc'와 같이 부모클래스에 선언된 동일한 이름 형태와 파라미터로 자식클래스에 정의하는 것.
-> First, Second, Third 클래스는 상속관계로 연결되어 있으며, 이들은 모두 'MyFunc' 함수를 통해 오버라이딩 관계를 형성하고 있다.
- 위 예제에서 컴파일러는 tptr이 Third형 포인터라는 사실을 기억한다. 따라서 이포인터 변수가 참조하는 객체에는 총 세 개의 MyFunc함수가 존재하고, 이들은 오버라이딩 관계를 갖기 때문에 가장 마지막에 오버라이딩한 Third 클래스의 MyFunc 함수가 호출되어야 한다는 것을 알고 있다. 그래서 위 예제에서 Third 클래스의 MyFunc 함수가 호출이 된다.
*오버라이딩과 오버로딩
: 부모 클래스와 동일한 이름의 함수를 자식 클래스에서 정의한다고 무조건 함수 오버라이딩 되는 것은 아니다.
매개변수의 자료형 및 개수가 다르면, 이는 함수 오버로딩이 되어, 전달되는 인자에 따라서 호출되는 함수가 결정된다.
가상함수(Virtual Function)
C++에서 가상 함수(virtual function)는 파생 클래스에서 재정의할 것으로 기대하는 멤버 함수를 의미합니다.
이러한 가상 함수는 자신을 호출하는 객체의 동적 타입에 따라 실제 호출할 함수가 결정됩니다.
- 위 예제에서는 오버라이딩된 함수를 재정의함에도 포인터 변수의 자료형에 따라 호출되는 함수가 달라지고 있다. 이를 해결하기 위한 키워드가 virtual이다.
1 2 3 4 5 | class First { public: virtual void MyFunc() { cout<<"FirstFunc"<<endl; } }; | cs |
-> 가상함수의 선언은 virtual 키워드의 선언을 통해 이루어진다. 가상함수가 선언되면, 이 함수를 오버라이딩 하는 함수도 가상함수가 된다. 위와같이 변경하고 다시 출력을 하면 아래와 같다.
[출력결과]
ThirdFunc
ThirdFunc
ThirdFunc
순수 가상함수(Pure Virtual Function)와 추상 클래스(Abstract Class)
순수가상함수 : 함수의 몸체가 정의되지 않은 함수
1 2 3 4 5 | class First { public: virtual void MyFunc() = 0; //순수 가상 }; | cs |
-> 함수에 0을 대입하고 있다. 이는 실제 대입이 아닌 '명시적으로 몸체를 정의하지 않았음'을 컴파일러에 알리는 것을 의미한다.
이렇게 되면 잘못된 객체의 생석을 막을 수 있고, 실제로 실행되지 않는 함수를 명확히 할 수 있게 된다.
이렇듯 하나 이상의 멤버함수를 순수 가상함수로 선언한 클래스를 가리켜 '추상 클래스(abstract class)'라 한다. 이는 완전하지 않은, 객체생성이 불가능한 클래스란 의미를 지닌다.
가상 소멸자(Virtual Destructor)
virtual로 선언된 소멸자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class First { private: char* strOne; public: First(char *str) { strOne = new char[strlen(str+1)+1]; } ~First() { cout<<"~First"<<endl; delete []strOne; } }; class Second: public First { private: char * strTwo; public: Second(char * str1, char * str2) : First(str1) { strTwo = new char[strlen(str2)+1]; } ~Second() { cout<<"~Second()"<<endl; delete []strTwo; } }; int main(void) { First * ptr= new Second("simple", "complex"); delete ptr; return 0; } | cs |
[실행결과]
~First();
-> 객체의 소멸을 First형 포인터로 명령하니, First 클래스의 소멸자만 호출되었다. 이러한 경우엔 메모리 누수(leak)가 발생하게 된다. 객체의 소멸과정에서는 delete 연산자에 사용된 포인터 변수의 자료형에 상관없이 모든 소멸자가 호출되어야 한다. 이를 위해선 소멸자에 virtual을 선언하면 된다.
1 2 3 4 5 | virtual ~First() { cout<<"~First"<<endl; delete []strOne; } | cs |
-> 가상함수와 마찬가지로 소멸자도 상속의 계층구조상 맨 위에 존재하는 부모 클래스의 소멸자만 virtual로 선언하면, 이를 상속받은 자식 클래스의 소멸자들도 모두 '가상 소멸자'로 선언이 된다.
가상 소멸자가 호출되면, 상속의 계층 구조상 맨 아래에 존재하는 자식 클래스의 소멸자가 대신 호출되면서, 부모 클래스의 소멸자가 순차적으로 호출된다.
~Third() -> ~Second() -> ~First()
[참고자료]
윤성우 열혈 C++ 프로그래밍 08 상속과 다형성
'Software Development > C, C++' 카테고리의 다른 글
[C++] 연산자 오버로딩 (0) | 2019.01.03 |
---|---|
[C++] 가상 상속 (Virtual Inheritance) (0) | 2019.01.02 |
[C++] 상속(Inheritance) (0) | 2018.12.31 |
[C++] C++에서의 static (0) | 2018.12.30 |
[C++] const, friend 키워드 (0) | 2018.12.30 |