[C++]

[C++] 가상함수와 추상 클래스 (다형성)

Jarvis2304 2022. 11. 17. 19:14

▶ 상속관계에서의 함수 중복

- 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() 함수를 호출

그림1

기본 클래스의 포인터 활용

- 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