C++에서의 가상 함수와 순수 가상 함수 정의

C++를 학습하거나 실무 개발을 수행하는 과정에서 “가상 함수(virtual function)”와 “순수 가상 함수(pure virtual function)”의 개념이 혼란스러운 경우가 매우 흔하다. 특히 다형성(polymorphism)과 상속(inheritance)을 설계할 때 어떤 상황에서 virtual 키워드를 쓰고, 언제 순수 가상 함수로 추상 클래스를 정의해야 하는지 모호할 수 있다. 이로 인해 발생하는 개발자의 불안은 구체적으로 다음과 같다. “이 코드가 왜 객체를 생성하지 못하지?”, “왜 이 함수는 오버라이드 하지 않으면 컴파일 에러가 나는가?”, “가상 함수와 순수 가상 함수가 런타임 성능에 어떤 영향을 주는가?” 등이다. 이러한 의문은 특히 C++의 OOP 활용이 증가하고, C++20/23/26까지 확장된 언어 기능과 함께 구조적 설계가 복잡해진 최근 상황에서 더욱 증가하고 있습니다.

포스트 이미지

개발자들은 흔히 “virtual은 단지 상속하면 자동 동작하는 것 아닌가?”, “순수 가상 함수는 추상 클래스인가?”라는 질문으로 고민한다. 그러나 이러한 개념적 혼돈은 설계 오류, 객체 생성 시점 버그, 런타임 다형성 오동작 등을 초래하여 프로젝트 전체 품질 지표에 부정적 영향을 준다. 실제로 C++ 추상 클래스 관련 문서에서도 이러한 개념이 명확하게 구분되어야 함을 강조하고 있다.

심층 분석: 가상 함수의 메커니즘과 순수 가상 함수의 본질

가상 함수는 기본적으로 런타임 다형성(runtime polymorphism)을 지원하기 위한 C++의 핵심 메커니즘이다. 가상 함수는 기본 클래스(base class)에서 선언되며, 파생 클래스(derived class)에서 재정의(override)될 수 있다. 이를 통해 동일한 인터페이스를 가지면서도 서로 다른 행동을 구현할 수 있다. 기본 클래스 포인터나 참조를 통해 실제 객체의 타입에 맞는 함수가 호출되도록 하는 것은 “dynamic dispatch”라고 하며, 대부분 C++ 컴파일러는 이를 위해 vtable(virtual method table) 구조를 생성한다.

반면, 순수 가상 함수란 기본 클래스에서 실제 구현체(body, definition)를 제공하지 않는 가상 함수이다. 순수 가상 함수는 선언만 존재하고, 이름 뒤에 = 0 구문이 붙는다. 이런 기본 클래스를 “추상 클래스(abstract class)”라고 하며, 추상 클래스는 직접 인스턴스화(instantiation)가 불가능하다. 순수 가상 함수는 파생 클래스에서 반드시 재정의해야만 해당 파생 클래스를 인스턴스화할 수 있다.

기술적으로 가상 함수는 기본 클래스 내에 정의(definition)와 선언(declaration)이 존재하지만, 순수 가상 함수는 선언만 존재한다. 그러나 예외적으로 순수 가상 함수도 기본 클래스 내에서 구현을 제공할 수 있지만, 해당 클래스는 여전히 추상 클래스로 남는다. 이러한 예외 처리 기법은 특히 순수 가상 소멸자(pure virtual destructor)를 구현할 때 자주 사용된다.

해결 솔루션 & 데이터

분류 가상 함수 순수 가상 함수 정량적 특성
정의 여부 기본 클래스 내 정의 가능 기본 클래스 내 =0 선언만 정의 유무(Boolean)
인스턴스 생성 가능 불가능(추상 클래스) 가능성: 100% vs 0%
오버라이드 요구 선택적 필수 오류 발생률: 선택적 vs 100% 요구
추상 클래스 아님 추상 클래스 여부
런타임 다형성 지원 지원 동적 바인딩 활용

위 표는 가상 함수와 순수 가상 함수의 본질적 차이를 정량적으로 비교한 것이다. 순수 가상 함수가 추상 클래스 생성의 원인이라는 점과, 오버라이드 조건이 필수적이라는 점이 주요 구분점이다.

가장 흔한 실무 적용 시나리오는 다음과 같다:

  1. 일반적인 런타임 다형성이 필요한 경우, 기본 클래스에 virtual 키워드를 선언하고 디폴트 구현을 제공한다.
  2. 기본 클래스가 전체 구현을 제공할 필요가 없고, 파생 클래스가 반드시 함수 정의를 제공해야 하는 경우 = 0으로 순수 가상 함수를 선언한다.
  3. 추상 클래스로부터 파생된 모든 클래스에서 순수 가상 함수가 완전히 오버라이드 되었는지 컴파일러 경고/오류를 확인한다(정량적 조건: 오류 없을 시 객체 생성 가능).
  4. 코드 유지보수 시 각 클래스의 바인딩 비용을 줄이기 위해 가상 함수 테이블(vtable)의 크기를 모니터링한다. 일반적으로 가상 함수 1개당 객체당 8~16바이트의 추가 메모리를 소모한다(컴파일러 및 플랫폼에 따라 다름).
  5. 디버그 빌드에서는 가상 함수 테이블 오버헤드가 10~20% 정도 성능 비용을 유발할 수 있으므로, 필요 시 최적화 빌드를 활용한다.

전문가 조언 & 팩트체크

  • 가상 함수와 순수 가상 함수 모두 런타임 다형성을 지원하지만, 순수 가상 함수는 추상 클래스를 생성하여 객체를 직접 만들 수 없게 한다.
  • 순수 가상 함수 선언 시 = 0 구문은 메모리 상에서 구현 유무를 나타내는 것이며, 이는 C++ 표준에서 “추상 함수”를 정의하는 정식 문법임.
  • 추상 클래스를 설계할 때는 인터페이스 역할을 명확히 정의하고, 필요한 함수에 대해서만 순수 가상 함수를 사용해야 유지보수성을 높일 수 있다.
  • 기본 클래스에서 순수 가상 함수에 대한 구현을 제공하는 것이 가능하지만, 클래스는 여전히 추상 클래스 상태로 유지됨을 오해하지 말아야 한다.
  • C++20 이상에서는 Concepts와 함께 가상 함수 설계 시 일반적인 함수 템플릿과 조합하여 인터페이스를 더욱 강하게 표현할 수 있다.
  • Constructor/Destructor 내부에서 가상 함수를 호출하면 기대한 다형성이 동작하지 않을 수 있으므로 주의해야 한다(구현 시점 제한).

알려드린 내용 참고가 되었다면 좋겠습니다.