[C++] 가상함수와 추상 클래스 (다형성)
▶ 상속관계에서의 함수 중복
- C++에서는 함수 중복(오버로딩)을 통해 기본 클래스의 함수와 동일한 함수를 파생 클래스에 선언가능
예제) 파생클래스에서 함수를 재정의
- 기본 클래스에 대한 포인터로는 기본 클래스의 함수를 호출
- 파생 클래스에 대한 포인터로는 파생 클래스의 함수를 호출
- 범위 지정 연산자(::)를 사용하면 상속에서 중복된 함수를 구분하여 호출가능
pDer -> f(); // Derived의 멤버 f() 호출
pDer -> Base::f(); // Base의 멤버 f() 호출
▶ 가상 함수와 오버라이딩
● 가상 함수(virtual function)
- virtual 키워드로 선언된 멤버 함수
- 동적 바인딩 지시어
- 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시
● 함수 오버라이딩(function overriding)
- 여기서는 virtual 키워드를 사용하면 함수 오버라이딩, virtual 키워드를 쓰지 않으면 함수 재정의으로 구분, (함수중복은 오버로딩)
- 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언
- 기본 클래스의 가상 함수의 존재감 상실시킴
- 파생 클래스에서 오버라이딩한 함수가 호출되도록 동적 바인딩
- 함수 재정의라고 부름
- 다형성의 한 종류
▷ 함수 재정의와 오버라이딩 비교
- 함수 재정의는, 컴파일 시간에 결정된 함수가 단순히 호출(정적 바인딩)
- 오버라이딩은 가상 함수를 재정의 하는 것으로, 함수가 호출되는 실행 시간에 발생(동적바인딩)
예제) 오버라이딩과 가상 함수 호출
▷ 오버라이딩의 목적
- 파생 클래스에서 구현할 함수 인터페이스 제공(파생 클래스의 다형성)
- Shape은 draw()를 가상 함수로 선언하였기 때문에, 파생 클래스들은 Shape의 draw()함수를 오버라이딩하여 자신만의 모양을 그리도록 코딩
- p->draw()는 p가 가리키는 객체 내에 오버라이딩된 draw() 함수를 호출하기 때문에 오버라이딩을 통한 다형성의 실현
▶ 바인딩
- 컴퓨터 프로그래밍에서 각종 값들이 확정되어 더 이상 변경할 수 없는 구속(bind) 상태가 되는 것
- 프로그램 내에서 변수, 배열, 라벨, 절차 등의 명칭, 즉 식별자가 그 대상인 메모리 주소 또는 실제값으로 배정되는 것
- 함수를 호출하는 부분에서 실제 함수가 위치한 메모리를 연결하는 것도 바인딩
- 구체적인 값을 할당하는 각각의 과정을 바인딩 ex) int num = 123;
▶ 정적 바인딩
- 실행 시간 전에 일어남
- 원시 프로그램의 컴파일링 또는 링크 시에 확정되는 바인딩
- 컴파일 시간에 결정된 함수가 단순히 호출
▶ 동적 바인딩
- 실행하는 동안 호출될 함수가 결정되며 포인터가 가리키는 객체에 따라 호출되는 함수가 유동적인 상태
- 실행 시간에 일어남
- 오버라이딩된 함수가 무조건 호출
- 객체 내에 오버라이딩한 파생 클래스의 함수를 찾아 실행
- 실행 시간 바인딩(run-time-binding) 혹은 늦은 바인딩(late binding)이라고도 함
● 동적 바인딩이 발생하는 구체적 경우
- 기본 클래스 내의 멤버 함수가 가상 함수 호출
- 파생 클래스 내의 멤버 함수가 가상 함수 호출
- 파생 클래스에 대해, 기본 클래스에 대한 포인터로 가상 함수를 호출
- main()과 같은 외부 함수에서 기본 클래스의 포인터로 가상 함수 호출
- 다른 클래스에서 가상 함수 호출
▷ 오버라이딩된 함수를 호출하는 동적 바인딩
▶ C++ 오버라이딩의 특징
● 오버라이딩의 성공 조건
- 가상 함수 이름, 매개변수 타입과 개수, 리턴 타입이 모두 일치
- 오버라이딩시 virtual 지시어 생략 가능 :기본클래스에서 virtual 키워드 선언시, 가상 함수의 virtual 지시어는 상속됨, 파생 클래스에서 virtual 생략가능
- 가상 함수의 접근 지정 :private, protected, public 중 자유롭게 지정 가능
예제) 상속이 반복되는 경우 가상 함수 호출
▶ 오버라이딩과 범위 지정 연산자(::)
- 정적 바인딩 지시
- 기본클래스::가상함수() 형태로 기본 클래스의 가상 함수를 정적바인딩으로 호출
예제) 범위 지정 연산자(::)를 이용한 기본 클래스의 가상 함수 호출
▶ 가상 소멸자
- 소멸자를 virtual 키워드로 선언
- 소멸자 호출 시 동적 바인딩 발생
- 기본 클래스의 소멸자를 만들 때 가상 함수로 작성하는 것을 권함
- 파생 클래스의 객체가 기본 클래스에 대한 포인터로 delete 되는 상황에서도 정상적인 소멸이 되도록 하기 위해서
예제) 소멸자를 가상 함수로 선언
▶ 오버로딩(overloading) vs 오버라이딩(overriding, 함수 재정의) 비교
▶ 가상 함수와 오버라이딩의 활용 예제
▷ 가상 함수를 가진 기본 클래스의 목적
- Shape은 가상 함수 draw()를 선언하여, 파생 클래스에서 draw를 재정의하여 자신만의 도형을 그리도록 유도
▷ 가상 함수 오버라이딩(다형성)
- 파생 클래스마다 다르게 구현하는 다형성
- 파생 클래스에서 가상 함수 draw()의 재정의
- 어떤 경우에도 자신이 만든 draw()가 호출되는 것을 보장 받음 (동적 바인딩에 의해)
▷ 동적 바인딩 실행
- 파생 클래스의 가상 함수 실행
- Circle, Rect, Circle, Line, Rect 객체를 순서대로 생성하여 링크드 리스트(Linked List)로 연결
- Shape 타입의 포인터를 이용하여 (그림1)과 같이 연결된 모든 도형을 방문하면서 paint() 함수를 호출하면, paint()는 동적 바인딩을 통해 Circle, Rect, Line 클래스에 재정의된 draw() 함수를 호출
▷ 기본 클래스의 포인터 활용
- pStart, pLast, p의 타입은 모두 기본 클래스 Shape에 대한 포인터이므로
- pLast는 Shape를 상속받은 어떤 객체든지 가리킬 수 있음
- p의 경우 링크드 리스트를 따라 이동하면서 Shape의 paint()를 호출하여, 동적 바인딩으로 각 파생 클래스의 draw()를 호출함
▶ 순수 가상 함수 (pure virtual fucntion)
- 함수의 코드가 없고 선언만 있는 가상 멤버 함수
▷ 목적
- 기본 클래스의 가상 함수 목적은 파생 클래스에서 재정의할 함수를 알려주는 역할(실행할 코드를 작성할 목적이 아님)
- 실행이 목적이 아니라면 굳이 기본 클래스의 가상 함수에 코드 작성필요 x → 순수 가상 함수 사용
▷ 선언 방법
- 멤버 함수의 원형 = 0; 으로 선언
▶ 추상 클래스
- 최소한 하나의 순수 가상 함수를 가진 클래스
▷ 특징
- 온전한 클래스가 아니므로 객체 생성 불가능
- 추상 클래스의 포인터는 선언 가능
▷ 목적
- 추상 클래스의 인스턴스(객체)를 생성할 목적 아님
- 상속에서 기본 클래스의 역할을 하귀 위함
- 순수 가상함수를 통해 파생 클래스에서 구현할 함수의 형태(원형)을 보여주는 인터페이스 역할
- 추상 클래스의 모든 벰버 함수를 순수 가상 함수로 선언할 필요 없음
▷ 추상 클래스의 상속과 구현
● 추상 클래스의 상속
- 추상 클래스를 단순 상속하면 자동 추상 클래스
● 추상 클래스의 구현
- 추상 클래스를 상속받아 순수 가상 함수를 오버라이딩 (파생 클래스는 추상 클래스가 아니게됨)
▷ Shape을 추상 클래스로 수정
예제) 추상 클래스 구현 연습
예제) 추상 클래스를 상속받는 파생 클래스 구현 연습
출처: 네이버 지식백과, 코딩의 시작, TCP School, 바인딩(Binding). 바인딩(Binding) 이란 프로그램의 어떤 기본 단위가 가질 수 있는… | by YE Ryu | POCS | Medium