Virtual 을 언제, 왜 사용해야하는가?

virtual function in Class

오버라이딩의 경우를 생각해보자.

class Parent{
    ...
};
class Child : public Parent{
    ...
}

다음과 같은 형식으로 class 가 구현되어있다고 해보자.

그런데 런타임에 어떤 클래스를 써야하는지 결정해야한다면 어떻게될까?

    Parent * p;
    Child * c;

    if(childIsSelected){
        //Child  를 선택한 경우는 Child 를 인스턴스화하고 함수실행.
        c = new Child();
        c->doFunction();
    }
    else{
        //Parent 를 선택한 경우는 Parent 를 인스턴스화하고 함수실행.
        p = new Parent();
        p->doFunction();
    }

이런식으로 두개를 모두 생성해놓고 사용하는건 굉장히 비효율적이고 코드상으로도 좋지 않다.

따라서 이러한 경우

    Parent * p;
    //Parent 포인터 하나만을 가지고 수행
    if(childIsSelected){
    	p = new Child();
    	p->doFunction();
    }
    else{
    	p = new Parent();
    	p->doFunction();
    }

Parent pointer 하나만 생성해놓고 사용할 수 있다. 즉 2개의 포인터를 관리하지 않고 하나만 가지고도 해결할 수 있는데 이때 virtual 이 사용된다.

class Parent{
    ...
    virtual void doFunction(){...}
};
class Child : public Parent{
    ...
    void doFunction(){...}
}

이런식으로 부모클래스에서 override 할 함수에 virtual 키워드를 붙이면 해결이 된다.

virtual 이라는 키워드는 컴파일러에게

“이 함수는 Parent 포인터에서 불렷지만 그게 아닐수도 있어!”

라는 정보를 준다. 따라서 실제 구동에서는 virtual function table 을 만들어서 virtual 함수가 호출된 경우에 table 을 확인해서 실제 어떤 오버라이드 함수를 실행해야하는지 결정하게한다.

따라서 virtual 은 어느정도의 오버헤드가 발생하게 된다. 자바계열에서는 모든 class member 함수가 virtual 함수라고 한다. c++ 에서는 키워드를 붙여야지만 virtual 이기 때문에 이를 잘 활용해보자.

주의할 점은 소멸자의 경우는 무조건 virtual 을 붙여야한다. 메모리 릭과 관련된 이슈가 발생할 수 있으므로 정확하게 오버라이드된 소멸자를 불러야하기 때문이다.

pure virtual function in Abstract class

virtual 키워드를 사용하는 약간은 다른 사용법은 추상 클래스에서다.

class Human{
    ...
    virtual void walk() = 0;
};
class Child : public Human{
    ...
    void walk(){...}
}
class Adult : public : Human{
    ...
    void walk(){...}
}

여기서 Human 은 무조건 걸어야된다고 생각해보자. 그런데 나잇대에 따라서 걷는 속도, 걸음걸이, 보폭 등이 다르기 때문에 서로 다른 walk 함수를 반드시 오버라이드 시켜야한다고 하자.

즉, Human 은 무조건 걸어야되고, 각 나잇대별로 무조건 함수를 오버라이드 해야하는 상황이다.

    Human h;
    h.walk();
    // Compile error

    Human * h2 = new Child();
    Human * h3 = new Adult();

순수 가상함수로 만들어진 Human 클래스는 생성이 안된다. 다만 포인터는 문제없이 만들 수 있다.

Human 클래스를 상속받는 클래스는 walk() 함수를 오버라이딩 해줘야만한다. (따라서 순사 가상함수는 반드시 public, protected 가 되야하며 private 으로 정의된 경우 오버라이드가 안된다)

이러한 Human 클래스를 추상 클래스라고 불리는데,

이를 사용해야하는 목적은 “설계도”의 기능입니다.

강제적으로 이 클래스의 하위 클래스는 미리 설계해 놓은 Human 클래스에서 사용할 순수 가상 함수를 직접 특수화해서 만들어야하는 것이다.