C++ 가상 함수 마스터: 개념, 예제, override 키워드
C++ 가상 함수
이 튜토리얼에서는 C++ 가상 함수의 목적과 실전 활용법을 코드 예제와 함께 설명합니다.
소개
가상 함수는 파생 클래스에서 오버라이드될 것으로 예상되는 기반 클래스 멤버입니다. 함수를 가상으로 선언하면 기반 클래스 포인터나 참조로 접근하더라도 가장 파생된 구현이 호출되도록 보장합니다.
기반 클래스에 non-virtual 멤버가 포함된 경우, 실제 객체 타입에 관계없이 기반 포인터로 해당 멤버를 호출하면 항상 기반 버전이 실행됩니다. 다음을 고려해 보세요:
class Base {
public:
void print() {
// 기반 구현
}
};
class Derived : public Base {
public:
void print() {
// 파생 구현
}
};
아래 프로그램에서 Base*가 Derived 인스턴스를 가리키지만 print()는 기반 버전으로 해석됩니다:
int main() {
Derived derived1;
Base* base1 = &derived1;
base1->print(); // Base::print() 호출
return 0;
}
다형적 동작을 얻으려면 기반 클래스에서 함수를 virtual로 선언하세요:
class Base {
public:
virtual void print() {
// 기반 구현
}
};
가상 함수는 C++ 다형성의 핵심입니다. 더 깊이 알아보려면 C++ 다형성 가이드를 참조하세요.
예제 1: 기본 가상 함수 사용
#include <iostream>
using namespace std;
class Base {
public:
virtual void print() {
cout << "Base Function" << endl;
}
};
class Derived : public Base {
public:
void print() override {
cout << "Derived Function" << endl;
}
};
int main() {
Derived derived1;
Base* base1 = &derived1;
base1->print(); // Derived Function 출력
return 0;
}
출력
Derived Function
Base::print가 virtual이므로 기반 포인터를 통해서도 Derived::print로 디스패치됩니다.
C++ override 식별자
C++11부터 override 키워드는 함수가 가상 기반 멤버를 진정으로 오버라이드하는지 컴파일러가 검증하도록 강제해 실수로 잘못된 오버라이딩을 방지합니다.
예제:
class Base {
public:
virtual void print() {
// 기반 구현
}
};
class Derived : public Base {
public:
void print() override {
// 파생 구현
}
};
오버라이딩 함수를 클래스 외부에서 정의할 때는:
class Derived : public Base {
public:
void print() override; // 선언
};
void Derived::print() {
// 구현
}
override 안전하게 사용하기
override가 없으면 오타나 시그니처 불일치가 조용히 새 함수를 생성해 오버라이딩이 아닌 결과를 낳으며 미묘한 버그를 유발합니다. 컴파일러가 즉시 오류를 내므로 문제를 바로 발견할 수 있습니다. 흔한 실수로는:
- 이름 불일치: 예:
print() 대신 pint()
- 반환 타입 차이: 예:
void 대신 int 반환
- 매개변수 불일치: 다른 매개변수 리스트
- 기반에 virtual 누락: 기반 함수가 아예 virtual이 아님
가상 함수의 실전 활용
Animal 기반 클래스와 파생 Dog, Cat 클래스를 고려해 보세요. 각 클래스는 생성자로 초기화되는 type 멤버를 가집니다.
class Animal {
private:
string type;
public:
Animal() : type("Animal") {}
virtual string getType() const {
return type;
}
};
class Dog : public Animal {
private:
string type;
public:
Dog() : type("Dog") {}
string getType() const override {
return type;
}
};
class Cat : public Animal {
private:
string type;
public:
Cat() : type("Cat") {}
string getType() const override {
return type;
}
};
getType()이 virtual이므로 단일 헬퍼로 모든 동물을 출력할 수 있습니다:
void print(Animal* ani) {
cout << "Animal: " << ani->getType() << endl;
}
main()에서의 사용:
int main() {
Animal* animal1 = new Animal();
Animal* dog1 = new Dog();
Animal* cat1 = new Cat();
print(animal1);
print(dog1);
print(cat1);
delete animal1;
delete dog1;
delete cat1;
return 0;
}
출력
Animal: Animal
Animal: Dog
Animal: Cat
getType()을 가상으로 만들어 각 파생 클래스에 print() 로직을 중복하지 않고도 올바른 type에 접근할 수 있습니다.