티스토리 뷰

반응형

상속 오버라이딩 함수 vs 가상 함수 (Overriding vs pure virtual function)

머릿속에 상속함수는 무엇이고, 가상함수는 무엇이다라고 정의는 되어있었지만, 둘이 어떻게 다른지 확실히 알지는 못했었다.

두개가 어떻게 다른지 말해보라고 하면, 말하기도 쉽지 않았다. (실제 코딩 인터뷰에서 이 문제를 물어봐서 당황하기도 했었어서) 정리하기 위해서 이 글을 쓴다.

1. 상속

상속이라는 개념은 오버라이딩으로 설명이 되는데, 클래스 A에 선언된 함수 func()가 있다고 가정하자. 그리고 클래스 B는 A로부터 파생된 함수, 즉 상속된 함수이고, 같은 함수 func()가 정의 된다.

다시 말해서, 기본(parent)클래스에서 정의된 멤버 함수를 파생된(child)클래스에서 같은형태로 선언하는 것을 말한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
class ParentClass{
public:
 void func(){ std::cout << "I am in ParentClass" << std::endl;}
} ;
 
class ChildClass : ParentClass{
public:
 void func(){ std::cout << "I am in ChildClass" << std::endl;}
} ;
 
int main(){
 ChildClass c;
 c.func();
}

위의 예제의 아웃풋은

I am in ChildClass

이다.  이렇게 결과가 나오는 이유는 기본함수의 func가 상속된 클래스 ChildClass에서 오버라이딩 되어있기 때문이다.  다시 말해, 같은 형태의 함수가 파생된 함수에서 다시 선언 됨으로써,  개존의 함수가 무시되고, 함수가 새롭게 재정의 (overriding) 되는 것이다.

잠깐!

오버라이딩은 오버로딩과 헷깔리면 안되는데, 오버라이딩은 위애서 설명한 것처럼,  모두 같은 형태(이름, 매개변수, 반환값 이 같은 형태)의 함수가 상속관계에서 재정의 되는것을 의미한다. 반면에, 오버로딩은 함수의 이름이 같지만, 매개변수 (input parameters, arguments)가 달라서 서로 다른 함수처럼 취급 되는것을 뜻한다. 오버로딩에서 반환값 (return value, type)은 상관 없다.

2.

물론 오버라이딩된 함수 뿐 아니라 Parent 에 정의된 함수도 접근 할 수 있는데, 이는 Parent (기존) 함수의 객체를 선언함으로써 가능해진다.

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
#include <iostream>
class ParentClass{
public:
 void func(){ std::cout << "I am in ParentClass" << std::endl;}
} ;
 
class ChildClass : public ParentClass{
public:
 void func(){ std::cout << "I am in ChildClass" << std::endl;}
} ;
 
int main(){
ChildClass* c = new ChildClass;
c->func();
 ParentClass* p = new ParentClass;
 p->func();
 
 ParentClass* pc = new ChildClass;
 pc->func();
 
delete c;
 delete p;
 delete pc;
 
}

위 코드의 아웃풋은 아래와 같다.

I am in ChildClass
I am in ParentClass
I am in ParentClass

ChildClass의 객체로 선언된 c는 ChildClass에 있는 func()를 실행하게 되고, ParentClass 의 객체로 선언된 p는 ParentClass에 있는 func()를 실행한다. 하지만, pc 의 경우 new ChildClass로 ChildClass를 가르키고 있지만, 실제 정의된 변수의 객체형인 ParentClass로 인식이 되어서, 기본함수에 정의되어있던 함수를 실행합니다.

3.

이제 실제 가르키고 있는 객체의 자료형을 기준으로 실행이 되게 하기 위해서는, 부모 함수, 즉 기본 함수를 기존과 다르게 정의해야 합니다. 이때 사용되는 것이 가상 합수 (Virtual function) 입니다. 이제 가상함수에 대해서 알아보겠습니다. 이름 그대로 가상함수는 실제로 아무런 실행도 하지 않지만, 가상적으로 함수는 정의됨으로써, 이를 상속하는 자식함수가 같은 함수를 같도록 강요하게 되며, 자식함수에서 정의된 함수가 실행되도록 만들어 줍니다. 이는 아래 코드와 같이 쓰일 수 있습니다.

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
</pre>
<span style="line-height: 1.5em;">#include <iostream></span>
 
class ParentClass{
public:
 virtual void func()=0;
} ;
 
class ChildClass : public ParentClass{
public:
 virtual void func(){ std::cout << "I am in ChildClass" << std::endl;}
} ;
 
int main(){
 
 ChildClass* c = new ChildClass;
 c->func();
 //ParentClass* p = new ParentClass;
 //p->func();
 ParentClass* pc = new ChildClass;
 pc->func();
 
delete c;
 
}

이의 결과는 다음과 같습니다.

I am in ChildClass
I am in ChildClass

여기서 한가지 중요한 점은 위의 코드에서 주석처리 된 부분입니다. 실제 주석을 제거하게 되면, 컴파일 에러가 나게 됩니다. 그 이유는 기본함수는 클래스는 실제 정의되지 않은(가상으로만 정의된) 함수를 포함하고 있으며, 이를 객체로 가르칠수 없게 되어버립니다. 이때의 클래스(가상함수를 포함하는 클래스)는 abstract class (추상 클래스) 라고 불리며, 객체를 선언할수 없습니다. 자세한 설명은 생략하겠습니다. 이때의 에러메시지는 다음과 같습니다.

 error: allocating an object of abstract class type ‘ParentClass’ (에러 : 추상 클래스 타입의 ‘ParentClass’에 객체 선언)

4.

그럼 상속과 가상함수에 대해서 간략하게 설명되었는데, 이들이 왜 필요한지에 대해서 생각해보겠습니다. 간단한 코딩에서는 사실 상속의 개념혹은 가상함수의 개념이 크게 중요하지 않을 수 있지만, 실제 회사에서 일을하거나, 큰 프로젝트의 코드를 짜게 되면 필수적으로 알아야 하는 부분입니다.

상속 (또는 가상함수)라는 개념을 통해서 우리는 코드를 짜는 동안에 동일한 인터페이스의 클래스를 가질수 있게됩니다. 예를들어, 운송수단이라는 클래스가 기본 클래스가 되고, 자동차, 오토바이, 또는 기차 등의 운송수단이 상속을 받은 자식클래스가 될 수 있는 것이지요. 그리고 운송수단에서 사용되는 구조나 클래스를 상속받음으로써 같은 구조를 가지지만, 오버라이딩 혹인 가상함수를 이용해 각각의 운송수단 개별의 함수를 가질 수도 있게됩니다.

그럼, 왜 가상함수라는것이 필요한가요? 라는 질문에 쉽게 대답하긴 힘듭니다. 개념적인 접근으로는, 위에 예제를 이용해서, 각각의 운송수단이 그들 자신을 대변하는 속성들이 있고 이는 클래스등에서 각자의 함수로 정의 될 수 있는데, 운송수단이라는 거대한 개념하나를 정의할 수는 없게 되지요. 이를 하나로 정의할 수 있는 속성은 쉽게 만들어 지지 않게 되며, 이를 정의할 필요도 없게 됩니다. 그래서 기본 클래스는 이들을 운송수단이라고 불리게 만들어주는 기본 껍데기 (구조, 가상의 정의)만을 위해 이용되게 됩니다.

5.

하지만, 여기서 여러개의 질문이 생깁니다. 1)  가상함수를 사용하지 않더라도, 오버라이딩을 통해서, 상속받은 자식함수에서 그들 자신의 함수를 재정의 할수 있는데, 왜  가상함수를 만들어야 하느냐? 2) 가상함수를 사용하지 않더라도, 자식함수의 객체를 선언함으로써 가상함수로 접근할 수도 있는데, 왜 이를 사용하느냐. 다시말해서, 가상함수를 사용하지 않더라도, 가상함수에서 필요한 모든것이 일반 상속에서 모두 구현 가능한데 왜 가상함수를 사용하는냐? 라는 질문들이 생긴다는 것입니다.


출처 : https://csstudy.wordpress.com/2014/01/21/%EC%83%81%EC%86%8D-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9-%ED%95%A8%EC%88%98-vs-%EA%B0%80%EC%83%81-%ED%95%A8%EC%88%98-overriding-vs-pure-virtual-function/

반응형

'면접공부해요' 카테고리의 다른 글

객체지향 OOP에 대한 정리  (0) 2018.04.26
C++과 C#의 차이점?  (0) 2018.04.26
단편화/멀티코어  (0) 2018.04.19
OOP - 포함, 상속, 오버라이딩  (0) 2018.04.19
C++/소멸자에 virtual을 쓰는 이유  (0) 2018.04.19
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2024/04   »
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
글 보관함