가상 함수

위키백과, 우리 모두의 백과사전.

객체 지향 프로그래밍에서 가상 함수(virtual function 또는 virtual method)는 상속하는 클래스 내에서 같은 시그니처의 함수로 오버라이딩 될 수 있는 함수 또는 메소드이다. 이 개념은 객체 지향 프로그래밍 (OOP)의 다형성에서 중요한 부분이다.

목적[편집]

가상 함수의 개념은 아래의 문제를 풀 수 있다.

OOP에서, 유도 클래스(derived class)가 기초 클래스(base class)를 상속할 때, 유도 클래스의 객체는 유도 클래스 타입이 아닌, 기초 클래스 타입의 참조 또는 여기에 대한 포인터로 여겨진다. 만약 기초 클래스 메소드가 유도 클래스에 의해 오버라이딩된다면, 실제로 참조나 포인터에 의해 호출되는 메소드는 '이전' (컴파일러의 경우) 또는 '나중' (언어의 런타임 시스템에 의해)에 바인드될 수 있다.

가상 함수는 '나중'에 바인드된다. 만약 기초 클래스에서 함수가 'virtual'이라면, 가장 유도된 클래스의 함수 구현은 참조되는 개체의 실제 타입에 달려있다. 포인터나 참조의 정의된 타입과 상관없이, 만약 'virtual'이 아니라면, 메소드는 '이전'에 바인드된다. 그리고 호출된 함수는 포인터나 참조의 정의된 타입에 따라 선택된다.

가상 함수는 코드가 컴파일될 때 심지어 존재할 필요가 없는 메소드도 호출할 수 있게 한다.

C++에서, 가상 메소드들은 기초 클래스에서virtual 키워드를 함수의 선언 시 추가함으로써 정의된다. 이 수식어는 계속 서로를 오버라이딩할 수 있고 추후 바인딩이 될 수 있다는 것을 의미하면서, 유도 클래스의 모든 구현들에 의해 상속된다.

예시[편집]

Animal의 클래스 다이어그램

예를 들면, 기초 클래스인 Animal는 가상 함수인 eat를 가질 수 있다. 하위 클래스 Fish 는 eat() 를 하위 클래스 Wolf와 다른 방식으로 구현할 것이다. 하지만 어느 클래스 인스턴스(Animal이라고 언급되는)에서라도 eat()를 발동할 수 있고, 특정한 하위 클래스의 eat() 행위를 얻을 수 있다.

class Animal {
    void /*non-virtual*/ move(void) {
        std::cout << "This animal moves in some way" << std::endl;
    }
    virtual void eat(void) {}
};

// The class "Animal" may possess a definition for eat() if desired.
class Llama : public Animal {
    // The non virtual function move() is inherited but not overridden
    void eat(void) {
        std::cout << "Llamas eat grass!" << std::endl;
    }
};

이것은 프로그래머가 클래스 Animal의 객체들의 목록을 처리할 수 있게 한다. 즉, 어떤 animal이 리스트에 있는지나 동물들이 어떻게 먹는지 등에 대해 알 필요 없이, 각각 차례로 eat()하게 할 수 있다.

추상 클래스와 순수 가상 함수[편집]

순수 가상 함수 (pure virtual function) 또는 순수 가상 메소드 (pure virtual method)는 유도 클래스(추상 타입이 아닌)에 의해 구현되는 가상 함수이다. 순수 가상 메소드들을 포함하는 클래스들은 "추상 클래스"로 불리며, 직접 인스턴스화 될 수 없다. 오직 추상 클래스의 하위 클래스만 직접 인스턴스화 될 수 있다. 순수 가상 메소드는 일반적으로 선언은 갖지만 정의는 갖지 않는다.

예를 들면, 추상 기초 클래스 MathSymbol은 순수 가상 함수 doOperation()을 제공하고, 유도 클래스 Plus와 Minus는 구체적인 구현을 제공하기 위해 doOperation()을 제공한다. doOperation()을 제공하는 것은 MathSymbol 클래스에서는 할 수 없다. 왜냐하면, MathSymbol은 이것의 하위 클래스에서만 정의된 추상 개념이기 때문이다. 비슷하게, MathSymbol의 하위 클래스도 doOperation()을 구현할 수 없다.

비록 순수 가상 메소드가 일반적으로 자신을 선언한 클래스 안에서 구현을 갖지 않지만, C++에서의 순수 가상 메소드는 적절한 경우에 유도 클래스가 기본 행위나 대비책을 위임함으로써 가질 수 있게 허용한다.

순수 가상 함수들은 또한 메소드 정의가 인터페이스를 정의하는데 쓰일 때 사용될 수 있다. 이것은 자바에서 인터페이스 키워드와 비슷하다. 이러한 사용에서, 유도 클래스들은 모든 구현을 제공할 것이다. 디자인 패턴 같은 경우, 인터페이스를 위한 추상 클래스는 오직 순수 가상 함수만 가지며, 데이터 멤버나 메소드들은 갖지 않는다. C++에서는 이러한 순수 추상 클래스들을 인터페이스 방식으로 사용한다. 왜냐하면 C++은 다중 상속을 지원하기 때문이다. 하지만 많은 OOP 언어들은 이것을 지원하지 않기 때문에, 종종 인터페이스 메커니즘을 따로 제공하여야 한다. 예를 들면 자바가 있다.

생성과 소멸 사이의 행위[편집]

객체의 생성자나 소멸자의 실행 중에, 언어들은 각자의 행위를 한다. 몇몇 언어에서는 (C++ 같은) 가상 디스패칭 메커니즘이 객체의 생성과 소멸 중에 다른 의미를 갖는다. 생성자 안에서 가상 함수를 호출하는 것은 C++에서 피해야 하지만,[1] 다른 언어들에서는, 예를 들면 C#과 Java, 유도 구현은 생성이나 design patterns시에 호출될 수 있다.

가상 소멸자[편집]

일반적으로 객체 지향 언어들은 객체들이 생성되거나 소멸될 때 자동적으로 메모리 할당과 회수를 관리한다. 그러나 몇몇 객체 지향 언어들은 커스텀하게 소멸자 메소드를 구현할 수 있게 허락하기도 한다. 만약 언어가 자동 메모리 관리를 사용한다면 호출된 컴스텀 소멸자는 객체에 적절하다고 확신할 것이다. 예를 들면, 만약 Animal을 상속하는 Wolf 타입의 객체가 생성되고, 둘 다 커스텀 소멸자를 갖는다면, 호출되는 것은 Wolf에 정의된 것이 된다.

수동 메모리 관리 상황에서, 특히 정적 디스패치와 관련된다면 상황은 훨씬 복잡해 진다. 만약 Wolf 타입의 객체가 생성되지만 Animal 포인터에 의해 가리켜지고, 이 Animal 포인터 타입이 삭제된다면, 소멸자는 Animal에서 정의된 것이 될 것이다. 이것은 특히 행위가 프로그래밍 에러의 공통된 소스인 C++의 경우에 그렇다.

같이 보기[편집]

각주[편집]