가상함수와 가상상속

Java에서는 당연한 C++의 '가상'


가상 함수

배경 및 필요성

  • 비가상함수의 경우 부모 클래스형 포인터로 자식 클래스의 멤버 함수를 호출할 때 컴파일러는 포인터의 자료형을 기준으로 멤버 함수를 호출한다. 즉, 실제 가리키는 객체의 자료형을 기준으로 판단하지 않는다. 자식 클래스에서 부모 클래스 함수를 오버라이딩 했다는 것은, 해당 객체에서 호출되어야 하는 함수의 행위가 바뀐다는 것인데 비가상함수의 경우 이를 지원하지 못한다. 이는 다형성 개념과 충돌된다. 따라서 객체지향에서는 가상 함수라는 개념을 제공한다. 가상 함수를 이용하면 포인터의 자료형을 기반으로 호출대상을 결정하지 않고, 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정한다. 이 개념은 객체 지향 프로그래밍의 다형성에서 중요한 부분이다.

동적 바인딩

  • 바인딩이란 함수 호출문에 대해 실제 호출될 함수가 위치한 메모리 번지를 연결해주는 것을 말한다. 바인딩 시간에 따라 정적 바인딩과 동적 바인딩으로 분류된다. 정적 바인딩은 컴파일 시간에 프로그래밍 문법을 바탕으로 함수 호출문과 함수가 위치한 메모리 번지를 연결한다. 동적 바인딩은 프로그램이 실행되고 있는 런타임 시간에 함수 호출문과 실제 함수의 메모리 번지와의 결합이 일어난다. 동적 바인딩은 가상함수 테이블을 이용하며 포인터(또는 레퍼런스)로 호출할 때만 일어난다. 동적 바인딩은 점프할 메모리 번지를 저장하기 위한 메모리 공간을 가지고 있다가 런타임에 결정되므로 타입 검사로 인한 수행 속도 저하와 메모리가 낭비된다는 단점을 가지고 있다. 하지만 이러한 단점에도 불구하고 동적 바인딩은 정확히 실 객체의 멤버 함수와 결합해주기 때문에 사용된다. 이렇게 동적 바인딩이 된 함수를 가상 함수라고 한다.

가상 함수 테이블

  • 가상 함수 테이블은 동적 바인딩을 지원하기 위해 프로그래밍 언어에서 사용되는 메커니즘이다. 동적 바인딩을 구현하는 여러 다른 방법들이 있지만, 가상 함수 테이블 방법은 C++과 관련된 언어들에서 주로 사용된다. 클래스가 가상 함수를 정의할 때마다, 클래스에 숨겨진 멤버 변수가 추가되는데, 이것은 함수들에 대한 포인터들의 배열(가상 함수 테이블)을 가리킨다. 컴파일 타임에는 부모 클래스의 함수가 호출될 것인지 자식 클래스에 의해 구현된 함수가 호출될 것인지 알려지지 않았기 때문에 가상 함수 테이블을 이용해 실행 기간 도중에 정확한 함수를 가리키게 된다.

프로그래밍 언어에서의 지원

  • 가상함수라는 개념은 객체지향의 개념이다. 따라서 여러 객체지향 언어에서 가상함수 개념의 문법이 제공된다. 적용하는 방법에는 약간의 차이를 보인다. 예를 들어 C++의 경우 virtual 키워드를 함수 선언부에 추가한다. JAVA의 경우 디폴트가 가상함수이므로 따로 키워드를 적어줄 필요가 없다. C++의 경우 부모 클래스에서 가상함수가 선언되고 나면, 이 함수를 오버라이딩 하는 함수도 가상함수가 된다.

추상 클래스와 순수 가상 함수

  • 순수 가상 함수란 함수의 몸체가 정의되지 않은 함수를 의미한다. 순수 가상 함수는 일반적으로 선언은 하지만 정의는 갖지 않는다. 즉 유도 클래스에 의해 구체적인 행위가 구현되는 가상함수다. 순수 가상 함수를 포함하는 클래스를 추상 클래스라고 하며 객체를 생성할 수 없다. 오직 추상 클래스의 하위 클래스만이 직접 인스턴스화 될 수 있다.

가상 소멸자

  • 일반적으로 JAVA와 같은 객체 지향 언어들은 객체가 생성되거나 소멸될 때 자동적으로 메모리 할당과 회수를 관리한다. 그러나 C++과 같은 몇몇 객체 지향 언어들은 프로그래머가 직접 소멸자 함수를 구현해 메모리 할당과 회수를 관리해야 한다. 수동으로 메모리를 관리해야 하는 상황에서 다형성을 이용해 참조한 객체를 소멸할 때 가상 소멸자를 이용하지 않으면 부모 클래스의 소멸자만 호출되어 메모리 누수가 발생한다. 따라서 이러면 virtual과 같은 가상 함수 키워드를 이용하여 소멸자를 가상 소멸자로 선언한다. 가상 소멸자가 호출되면, 하위 클래스의 소멸자부터 상위 클래스의 소멸자가 순차적으로 호출된다.

가상 상속

다중상속

  • 다중상속이란, 둘 이상의 클래스를 동시에 상속하는 것을 말한다. JAVA는 다중 상속을 지원하지 않지만 C+++은 다중상속을 지원하는 객체지향 언어다. 일반적으로 다중상속은 다양한 문제를 동반하므로 다중상속에 대한 프로그래머들의 다양한 견해가 있다.

다중상속의 모호성

  • 다중상속의 대상이 되는 두 기초 클래스에 같은 이름의 멤버가 존재하는 경우에 문제가 발생할 수 있다. 이러면 유도 클래스 내에서 멤버의 이름만으로 멤버에 접근할 경우 두 기초 클래스 중 어떤 클래스의 멤버에 접근하는 것인지에 대한 모호성이 발생한다. 다중상속의 모호성을 해결하기 위해서는 어느 클래스에 정의된 함수의 호출을 원하는지 명시를 통해 해결할 수 있다.

가상상속

  • 클래스의 상속 과정에서 같은 클래스가 두 번 이상 나타나 불필요한 자원이 소모되는 것을 방지하기 위해 나온 것으로 같은 클래스군에서 두 번 이상 나타나는 클래스를 가상으로 지정하면 상속 구조에서 몇 번이 나오더라도 오직 한 번만 서브 객체를 생성하게 하는 것이다.