객체 지향 프로그래밍에서 가상 함수(virtual function)는 다형성(polymorphism)을 구현하는 핵심 개념입니다. 흥미롭게도 C++와 Java는 이 가상 함수를 다루는 기본 동작 방식이 서로 정반대입니다. 두 언어의 차이를 명확히 이해하면 오버라이딩 관련 버그를 예방하고 더 안전한 코드를 작성할 수 있습니다.
C++의 경우: 기본적으로 비가상(Non-virtual)
C++에서 클래스의 멤버 함수는 기본적으로 비가상(non-virtual)입니다. 즉, 별도의 키워드 없이 선언된 일반 멤버 함수는 컴파일 시점에 호출이 결정되는 정적 바인딩(static binding)으로 동작합니다.
함수를 가상으로 만들고 싶다면 반드시 virtual 키워드를 명시적으로 붙여야 하며, 이렇게 선언된 함수는 런타임에 실제 객체 타입에 따라 호출되는 동적 바인딩(dynamic binding)을 따르게 됩니다.
Java의 경우: 기본적으로 가상(Virtual)
반면 Java에서는 모든 인스턴스 메서드가 기본적으로 가상(virtual)입니다. 별도의 키워드 없이도 자식 클래스에서 해당 메서드를 자유롭게 오버라이딩할 수 있습니다.
만약 특정 메서드의 오버라이딩을 금지하고 싶다면 final 키워드를 사용해 비가상처럼 동작하도록 만들 수 있습니다. 즉, C++와 Java는 정확히 반대되는 철학을 채택하고 있는 셈입니다.
C++ vs Java 비교 요약
| 구분 | C++ | Java |
|---|---|---|
| 기본 동작 | 비가상(non-virtual) | 가상(virtual) |
| 가상으로 만들기 | virtual 키워드 명시 | 기본값 (키워드 불필요) |
| 비가상으로 만들기 | 기본값 (키워드 불필요) | final 키워드 사용 |
Java 예제 코드
다음은 Java에서 메서드가 기본적으로 가상으로 동작함을 보여주는 간단한 예제입니다.
class base_class{
public void display_msg(){
System.out.println("The display_msg method of base class class");
}
}
class derived_class extends base_class{
public void display_msg(){
System.out.println("The display_msg of derived class called");
}
}
public class Main{
public static void main(String[] args){
base_class my_instance = new base_class();
my_instance.display_msg();
}
}
실행 결과
The display_msg method of base class class
코드 설명
먼저 base_class라는 클래스를 생성하고, 그 안에 화면에 메시지를 출력하는 display_msg 함수를 정의합니다. 이어서 base_class를 상속받는 derived_class를 만들고, 같은 이름의 display_msg 메서드를 오버라이딩하여 다른 메시지를 출력하도록 합니다.
Main 클래스의 main 함수에서는 base_class 타입의 인스턴스를 생성한 뒤 해당 인스턴스로 display_msg를 호출합니다. 부모 클래스 타입의 객체를 사용했기 때문에 부모 클래스의 메서드가 실행되며, 결과적으로 "The display_msg method of base class class"라는 출력이 화면에 표시됩니다.