본문 바로가기
Programming/c++ Design Pattern

[C++] 가상 함수

by 드가보자 2023. 11. 28.

함수는 함순데 가상함수가 뭐야 ?? Virtual Function ??

 

#include <iostream>

class Animal
{
public:
	void cry1() { std::cout << "Animal Cry1" << std::endl; } 
	virtual void cry2() { std::cout << "Animal Cry2" << std::endl; }
};

class Dog : public Animal
{
public:
	// function override
	void cry1() { std::cout << "Dog Cry1" << std::endl; } 
	//animal 이 가지고 있는 cry가 마음에 안들어 다시 만들래 . -> override
	void cry2() override { std::cout << "Dog Cry2" << std::endl; }
};

int main()
{
	Animal a; a.cry1();
	Dog d; d.cry1();

	Animal* p = &d; 
	
	p->cry1();
	p->cry2();
}

 

기반 Class Animal에서는 cry1 함수는 가상 함수가 아닌 것으로 , cry2 함수는 virtual로 만들었다.

virtual 함수는 Runtime에 어떤 함수를 실행할 것인지 판단 한다.

 

1. a.cry1 => Animal 멤버 함수인 cry1 함수를 호출한다.

2. d.cry1 => Dog 멤버 함수인 cry1 함수를 호출한다.

3. p->cry1 => Compiler가 음 ~ Animal pointer네 그럼 Animal Cry1 쓰면 되겠다~~~ 이렇게 생각하고 치워버린다.

4. p->cry2 => Compiler가 cry2 함수를 보니 뭐야?? 얘 virtual 함수네?? 지금 내가 결정할 수 없겠는데? 라고 판단을 하고 Runtime에서 실제 p가 가르키는 객체가 뭔지 판단을 한다.

 

[Compile Time]

"?? 얘 Virtual이네 내가 판단할 수 없어"

 

[RunTime]

"OK~ p 따라가서 cry2 함수 보니까 virtual이네?? 그럼 얘 객체가 누군지 확인해야겠는데..."

"가상함수 테이블 보니까 얘 Dog 객체네 -> Dog로 넘어가서 cry2 함수 실행" 

만약 Dog가 cry2 함수를 따로 구현하지 않았다면 그냥 Animal의 cry2 함수를 실행한다. 

 

그리고 p->cry1 이런데서 실제로 어느 함수를 사용할 것인가?? matching 하는 과정을 function binding이라고 부른다.

 

Dog class에 cry2 함수 뒤에 override 라는 문구가 있는데, 얘는 기반 클래스의 cry2 함수가 마음에 안들어!

그냥 내가 정의 할께 이런 느낌이다.

 

// 2_가상함수재정의
class Base
{
public:
	virtual void foo() {}
	virtual void goo(int) {}
};
class Derived : public Base
{
public:

	virtual void fooo() {} //1번
    virtual void goo(double){} //2번
    
    virtual void fooo() override {} //3번
    virtual void goo(double) override{} //4번
    
    virtual void foo() override
    {
    	std::cout<<"야호!!!"<<std::endl;
    }
};

int main()
{
}

 

이렇게 만들었다고 생각하자.

1번과 2번 같은 경우를 생각하자. 

이 때는 Compile Error 절대 발생하지 않는다. Compiler는 Derived class는 그럼

 

부모 클래스의 foo() goo(int) , 자기가 만든 fooo() goo(double) 총 4개의 함수를 사용할 수 있구나!! 

 

라고 인식을  한다. 사실은 난 foo() goo(int)를 내가 재정의 하고 싶었는데 오타를 내서 잘못 만든건데 말이야..

생각보다 코딩을 하면 오타가 나거나 실수하는 경우가 종종 있다. Compile Error가 나지 않아서 눈치 못채는 경우도 있다.

 

3번과 4번 경우는 override를 뒤에 붙여줬기 때문에, Compiler는 인식한다.

"아 !! 얘 부모 class에 있는 함수를 재정의 하는거구나!" => "잉 ?? 근데 부모 클래스에 fooo 함수랑 goo(double) 함수 없는데??" => "Compile Error 발생"

이렇게 된다면 내가 실수한 부분을 Compiler가 catch 해주었으니 정말 고마운 일이다. 

 

그래서 부모 클래스 함수를 파생 클래스에서 재정의 할 때 override 를 안 붙여도 되지만, 이런 실수를 방지하기 위해서 꼭 적어주는게 좋다. 파생 클래스의 앞부분 virtual은 빼줘도 된다. 사실 어차피 override가 virtual 뜻도 내장하고 있기 때문이다. 

 

그리고 순수 가상 함수라는게 있는데 

부모 class에서 virtual void foo()=0 이렇게 되어있으면 얘는 순수 가상함수이다.

뭐가 다르냐면 , 위에 코드 예시 경우에는 파생 클래스에서 재정의를 해주지 않으면 그냥 부모 클래스의 함수를 그대로 쓰는데 얘는 부모 클래스에서 정의를 하지 않았다.

virtual void foo()=0 => 순수 가상함수 => 얘는 "너네 파생 클래스들 무조건 이 함수 정의해라!" 이런 뜻이고

파생 클래스에서 순수 가상함수들을 정의 하지 않는다면 compiler error 발생한다. 

'Programming > c++ Design Pattern' 카테고리의 다른 글

[c++ Design Pattern] Template Method  (0) 2023.11.29
[C++] OCP Code 예제  (1) 2023.11.29
[C++] 추상 클래스  (0) 2023.11.29
[c++] Upcasting  (0) 2023.11.28
c++ 생성자  (0) 2023.11.28