가상함수 (Virtual)
: 클래스의 실제 타입이 무엇인지 확인하면서 실행하게 하려면 virtual 키워드를 이용
오버라이딩 할 때, 부모클래스의 함수에서
virtual 키워드를 추가하게 되면 정상적으로 우리가 원하는 오버라이드된 함수를 찾아가서 실행하게 된다.
이렇게 virtual을 추가해주어야 하는 이유는
함수가 virtual이 아닐 때 더 빠른 실행속도가 보장되기 때문
Java / C# 에서의 virtual
C#에는 virtual이 존재한다.
Java에는 모든 클래스의 멤버함수가 virtual이다.
이는 C#은 어느정도 이런 성능을 생각하고 만들었으며,
Java는 프로그램 개발의 편의성을 생각했기 때문에 그렇다.
자바에서는 다 virtual로 동작
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstring> class Animal { public: int legs; char name[20]; Animal(int legs, const char* name) { // 초기화 this->legs = legs; strcpy(this->name, name); } virtual void printInfo(){ printf("다리: %d, 이름 : %s\n", legs, name); } }; class Human : public Animal{ // 상속관계에서만 동작하는게 오버라이드 public: char regist_no[40]; Human(const char* regist_no) : Animal(2, "사람") { // 부모클래스에서 생성자를 정의했다면 그 생성자를 여기서 호출 strcpy(this->regist_no, regist_no); } // Human에서 다리개수, 이름 뿐만 아니라 주민번호까지 출력 하고 싶은 경우 -> Human에서 똑같은 이름으로 함수 만들기 virtual void printInfo() { // 파라미터까지 똑같은 함수를 만들어 내는 것 : 오버라이드 printf("이름: %s, 주민등록번호: %s\n", name, regist_no); } }; int main(){ Human* human = new Human("1234-5678"); Animal* animal = human; // animal에 human 이라는 클래스 인스턴스가 들어감 human->printInfo(); // virtual 함수이기 때문에 상속받은 다른 클래스가 실제 값인지를 확인하는 과정 거치다 맞는 함수를 실행 return 0; } |
소멸자에서의 virtual 키워드
소멸자에 virtual 키워드를 넣지 않게 되면
상속받은 클래스일 경우 클래스의 현재 타입에 맞는 소멸자를 호출하게 된다.
이는 메모리 누수가 발생할 수 있는데
이를 방어하기 위해 소멸자 앞에 virtual 키워드를 꼭 붙여야 한다.
virtual 키워드가 현재 instance의 실제 타입을 찾아가서 그 타입의 함수를 실행한다.
다만 소멸자는 소멸자가 실행된 순간부터 부모에 있는 소멸자들을 모두 호출하기 때문에
소멸자는 위쪽에 있는 소멸자들을 다 호출 해 주게 되는 것
virtual 키워드는 실질적으로 내가 원하는 함수호출을 하기 위해서 중요하지만
c++ 에서는 소멸자를 제대로 호출하기 위해서도 꼭 필요한 키워드
#define _CRT_SECURE_NO_WARNINGS #include <cstdio> #include <cstring> class Animal { public: int legs; char* name; // 동적 배열 Animal(int legs, const char* name) { // 초기화 this->name = new char[strlen(name) + 1]; // 동적배열을 할당해서 받은 크기만큼의 문자열 길이를 동적으로 할당 this->legs = legs; strcpy(this->name, name); } virtual~Animal(){ printf("Animal 소멸자 호출됨\n"); delete [] name; // 동적으로 할당된 배열을 heap메모리에서 제거 } void printInfo(){ printf("다리: %d, 이름 : %s\n", legs, name); } }; class Human : public Animal{ // 상속관계에서만 동작하는게 오버라이드 public: char* regist_no; Human(const char* regist_no) : Animal(2, "사람") { // 부모클래스에서 생성자를 정의했다면 그 생성자를 여기서 호출 this->regist_no = new char[strlen(regist_no) + 1]; strcpy(this->regist_no, regist_no); } // Human에서 다리개수, 이름 뿐만 아니라 주민번호까지 출력 하고 싶은 경우 -> Human에서 똑같은 이름으로 함수 만들기 virtual~Human(){ printf("Human 소멸자 호출됨\n"); delete [] regist_no; } void printInfo() { // 파라미터까지 똑같은 함수를 만들어 내는 것 : 오버라이드 printf("이름: %s, 주민등록번호: %s\n", name, regist_no); } }; int main(){ /* Animal* animal = new Human("1234-5678"); // virtual 키워드를 소멸자 앞에 붙여주지 않으면 이 클래스 인스턴스의 타입은 Animal의 포인터 : Animal의 소멸자만 호출 Human 소멸자는 호출하지 않는다. delete animal; // 메모리 누수 발생 Human 소멸자 호출되지 않는다. 소멸자 앞에 virtual ~Human() 이런식으로 virtual을 붙여주면 실행했을때 정상적으로 소멸자 동작 */ Human* human = new Human("1234-5678"); delete human; // 이 경우 Human소멸자 Animal소멸자 둘 다 정살 호출됨 // 자식 소멸자를 호출하면 부모 소멸자까지 연쇄적으로 전부 다 호출한다. return 0; } |
'C++' 카테고리의 다른 글
c++ 8 (추가 개념 정리 / 피드백) (0) | 2022.02.05 |
---|---|
c++ 6 (열거형 - 가독성을 위한 타입) (0) | 2022.01.23 |
c++ 4 (오버라이드 / 오버로드) (0) | 2022.01.13 |
c++ 3 (클래스 상속 / 접근지정자) (0) | 2021.09.24 |
c++ 2 (클래스 멤버 / 생성자 / 소멸자) (0) | 2021.09.08 |