본문 바로가기

C++

c++ 5 (가상함수 - Java, C# 등 다른 언어의 개념 까지 )

가상함수 (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;
}