프로그래밍/cpp

가상함수(2)

콘파냐 2014. 1. 7. 22:27
반응형

가상함수(함수내 함수호출,함수내 가상함수호출),함수 전달인자

이번 포스팅은 가상함수의 동작을 조금 더 깊게 들어가보기로 하겠다.

 

 

2014/01/04 - [프로그래밍/c++] - 가상함수(virtual),vptr,vtable C++

 

2013/06/04 - [프로그래밍/c++] - 가상함수 virtual 키워드 C++

 

전시간에는 가상함수 테이블에 관련하여 내부적으로 가상함수가 어떻게 동작하는지 살펴보았다.

유연한 프로그래밍을 위해서는 이런 내부적인 동작을 머리속에 넣어 두고 있으면 많은 도움이 된다.

이번시간또한 내부적인 동작을 다뤄볼 텐데, 클래스내부의 함수로 (가상)함수를 호출하는 경우를 다뤄볼 것이다.

함수의 상속과 호출이 어떤식으로 이뤄나는지에대해서 살짝 깊게 들어가려한다.

사실 함수의 상속은 단순히 하위클래스로 전부 된다고 알고 있다. 과연 이말이 맞는 것일까? 좀더 정확히 살펴보자.

다음 코드를 살펴보자.

 

 

어렵지 않기 때문에 별다른 설명은 않도록한다.

결과를 예상해보자.

.

.

.

4가지 결과가 나와야 하고, 그 결과를 어떻게 예상하는가?

그전에 몇가지 질문을 해보겠다.

1. B클래스는 A클래스의 멤버함수를 상속했기때문에 run()함수가 포함되어있다. 때문에 b.run()은 B의 멤버함수 test()를 실행한다?

2. 위 코드상에서 B::run()은 문제없이 동작한다. 그럼 B::run()은 존재하는가? 그럼 위 실행결과는 어떻게 예상하는가?

3. 위 결과를 예상해보자.

.

.

.

상속이란, 상위클래스의 멤버함수와 멤버변수를 그래도 쓸 수 있다는 의미이다.

여기서는 멤버변수가아닌 멤버함수만 다루고 있기때문에 멤버함수에 관해서 쓰겠지만, 그전에 객체가 생성될 때 멤버함수와 멤버변수가 어떤식으로 다뤄지는지 간략하게 그림을 그려보겠다.

 

 

A와 B라는 두개의 객체를 생성하였다. 멤버변수 x,y는 객체별로 생성된다. func()라는 멤버함수는  두 객체가 같이사용한다.

멤버함수의 공유가 가능한 이유는 함수의 경우는 인자값이 동일하면 결과값도 동일하기때문이다. 그럼 어떤객체로 호출하든 결과값이 같아지는가? 답은 그렇지 않다.

여기서 중요한 설명을 하고 넘어가려한다. 위 그림에서 func()의 인자값은 몇개인가?

0개라 생각할지모르겠지만 내부적으로는 자신을 호출한 객체의 번지주소값을 인자로 넘겨주게된다.

func(&A), func(&B) 이런식으로 번지값을 넘겨준다. 이것은 함수가 자신을 호출한 객체를 아는 방법이고, 객체에따라서 함수값이 변화되는 요인이기도하다.(객체에 따라서 내부 멤버변수의 값이 다를 수 있기때문에) 이러한 내부적인 동작을 살펴보았다면 다시 주제로 넘어가서 코드의 결과는 어떻게 될까?

4가지 함수 호출 모두 모두 test_A 라는 결과가 나온다.

 

왜그럴까 생각을해보고 해결방법을 찾아보면서 설명을 하겠다.

a.run(); //당연히 test_A

b.run(); // 상속되었지만, 여기서의 상속은 자식클래스의 객체가 단순히 부모클래스의 멤버함수를 빌려쓰는 형식, 다시말해 B::run()은 결국 A::run()을 가리킨다. 그래서 test_A

b.B::run(); //위에서 설명했고.

c.C::run(); // 이경우는 C::run()->B::run()->A::run()이 된다. 그래서 test_A

4가지경우 결과는 동일하고, 동일한 이유는 run()함수가 단순히 상속만 되었기 때문이다.

다시말해 단순히 상속된 경우 상위클래스로 링크만 된다. 만약 링크가 되지않고 컴파일시 상속되는 모든 멤버 함수코드가 자식클래스에 삽입이 된다면, 계층구조가 복잡하고 많아질수록  프로그램은 엄청나게 부피가 커질 것이다.

 

하지만 위 코드가 의도한 결과는 객체별로  자신의 클래스타입에 맞는 함수를 호출하는 것이다.(다형성의 목적이기도하다.)

그래서 우리가 지금까지 가상함수를 배운것이다.

test() 멤버함수를 가상함수로 만들어보면 우리가 원하는 결과가 나온다.

test_A

test_B

test_B

test_C

살펴보자

a.run();은 내부적으로는 run(&a)이고 여기서 run()은 A::run()이다.

run()을 호출하는 this는 A클래스의 포인터이고 A *this = &a; 관계가 성립된다. 결국 A클래스포인터가 호출하는 test는 동적결함을 하게된다.

같은 맥락으로 b.run()은 b객체타입에 동적결함을하게되고

c.C::run()은 위 코드에선 c.A::run()과 동일하므로 C클래스의 test()가 실행된다.

간단히 직접 코드를 작성해보면 그렇게 어렵지는 않다.

반응형