공부/C++

11. 가상함수와 추상클래스

bokob 2024. 6. 2. 03:25

11.1 가상함수

상속 관계에서 함수를 중복하는 경우

 

가상 함수와 오버라이딩

가상 함수(virtual function)

  • virtual 키워드로 선언된 멤버 함수
  • virtual 키워드의 의미
    • 동적 바인딩 지시어
    • 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시
class Base{
public:
    virtual void f(); // f()는 가상 함수
};

 

함수 오버라이딩(function overriding)

  • 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언, 내부 로직 빼고는 완전히 동일해야 함
    • 기본 클래스의 가상 함수의 존재감을 상실시킴
    • 파생 클래스에서 오버라이딩 함수가 호출되도록 동적 바인딩
    • 함수 재정의라고도 부름
    • 다형성의 한 종류

 

 

오버라이딩과 가상 함수 호출

 

오버라이딩: 파생 클래스에서 구현할 함수 인터페이스 제공

다형성 실형 예:

  • draw() 가상 함수를 가진 기본 클래스 Shape
  • 오버라이딩을 통해 Circle, Rect, Line 클래스에서 자신만의 draw() 구현

 

동적 바인딩

동적 바인딩

  • 파생 클래스에 대해, 기본 클래스에 대한 포인터로 가상 함수를 호출하는 경우
  • 객체 내에 오버라이딩한 파생 클래스의 함수를 찾아 실행
    • 실행 중에 이루어짐: 실행시간 바인딩, 런타임 바인딩, 늦은 바인딩으로 불림

 

C++ 오버라이딩의 특징

오버라이딩의 성공 조건

  • 가상 함수 이름, 매개 변수 타입과 개수, 리턴 타입이 모두 일치

  • 오버라이딩 시 virtual 지시어 생략 가능
    • 가상 함수의 virtual 지시어는 상속됨, 파생 클래스에서 virtual 생략 가능
  • 가상 함수의 접근 지정
    • private, protected, public 중 자유롭게 지정 가능

 

상속이 반복되는 경우 가상 함수 호출

Base, Derived, GrandDerived가 상속 관계에 있을 때,

 

오버라이딩과 범위 지정 연산자(::)

범위 지정 연산자(::)

  • 정적 바인딩 지시
  • 기본클래스::가상함수() 형태로 기본 클래스의 가상 함수르 정적 바인딩으로 호출

 

범위 지정 연산자(::)를 이용한 기본 클래스의 가상 함수 호출

 

가상 소멸자

소멸자도 가상함수로 선언해야 하는 경우

  • 클래스에  가상함수가 있고, 클래스가 반드시 소멸자를 사용해야 할 때

ex) 동적으로 생성한 파생 클래스의 객체를 기본 클래스 포인터로 가리킬 때

-> 기본 클래스 포인터로 delete 하더라도 파생 클래스의 소멸자가 호출되도록 해야 함

  • 가상 소멸자
    • 소멸자를 virtual 키워드로 선언
    • 소멸자 호출 시 동적 바인딩 발생
Shape* pShape = new Rectangle;
delete pShape; // Rectangle 소멸자가 호출되도록 하려면 Shape 소멸자를 가상함수로 만든다.
class Shape{
    ...
    virtual void Draw() const;
    ...
    virtual ~Shape();
};

 

가상 소멸자

 

소멸자를 가상함수로 선언

 

오버로딩과 오버라이딩 비교

비교 요소 오버로딩 오버라이딩
정의 매개 변수 타입이나 개수가 다르지만, 이름이 같은 함수들이 중복 작성되는 것 기본 클래스에 선언된 가상 함수를 파생 클래스에서 이름, 매개 변수 타입, 매개 변수 개수, 리턴 타입까지 완벽히 같은 원형으로 재작성하는 것
존재 외부 함수들 사이. 한 클래스의 멤버들. 상속 관계 상속 관계. 가상 함수에서만 적용
목적 이름이 같은 여러 개의 함수를 중복 작성하여 사용의  편의성 향상 기본 클래스에 구현된 가상 함수를 무시하고, 파생 클래스에서 새로운 기능으로 재정의하고자 함
바인딩 정적 바인딩. 컴파일 시에 중복된 함수들의 호출 구분 동적 바인딩. 실행 시간에 오버라이딩된 함수를 찾아 실행
관련 객체지향 특성 다형성 다형성

 

11.2 가상함수와 오버라이딩 활용사례

가상 함수를 가진 기본 클래스의 목적

 

가상 함수 오버라이딩

파생 클래스마다 다르게 구현하는 다형성

void Circle::draw(){cout<<"Circle"<<endl;}
void Rect::draw(){cout<<"Rectangle"<<endl;}
void Line::draw(){cout<<"Line"<<endl;}

파생 클래스에서 가상 함수 draw()의 재정의

  • 어떤 경우에도 자신이 만든 draw()가 호출됨을 보장 받음
    • 동적 바인딩에 의해

 

동적 바인딩 실행: 파생 클래스의 가상 함수 실행

 

main() 함수가 실행될 때 구성된 객체의 연결

 

기본 클래스의 포인터 활용

기본 클래스의 포인터로 파생 클래스 접근

  • pStart, pLast, p 타입이 Shape*
  • 연결 리스트를 따라 Shape을 상속받은 파생 객체들 접근
  • p->paint()의 간단한 호출로 파생 객체에 오버라이딩된 draw() 함수 호출

 

11.3 추상 클래스와 인터페이스 상속

순수 가상함수

기본 클래스의 가상 함수 목적

  • 파생 클래스에서 재정의할 함수를 알려주는 역할
    • 실행할 코드를 작성할 목적이 아님
  • 기본 클래스의 가상 함수를 굳이 구현할 필요는 없음(필요에 따라)

 

순수 가상 함수

  • 함수의 코드가 없고 선언만 있는 가상 멤버 함수
  • 파생 클래스에서 재정의될 함수에 대해서 미리 원형이 필요하기는 하지만 기본 클래스에서는 아직 구현할 필요가 없을 때 사용
  • 순수 가상 함수는 "파생 클래스에서 재정의하도록 원형만 미리 준비해두는 것"이라는 의미가 되며, 파생 클래스는 상속받은 순수 가상 함수를 반드시 재정의해야 한다.

 

순수 가상 함수 선언 방법

  • 함수 선언 끝 부분에 "=0"을 적어줌
class Shape{
public:
    virtual void draw() = 0; // 순수 가상 함수
};

 

추상 클래스

추상 클래스(abstract class)

  • 최소한 하나의 순수 가상 함수를 갖는 클래스
class Shape{ // Shape은 추상 클래스
protected:
    int _x;
    int _y;
public:
    void paint();
    virtual void draw()=0; // 순수 가상 함수
};

void Shape::paint(){
    draw(); // 순수 가상 함수라도 호출은 할 수 있다.
}

 

추상 클래스 특징

  • 온전한 클래스가 아니므로 추상 클래스는 객체를 생성할 수 없다.
Shape shape; // 컴파일 오류
Shape *p = new Shape(); // 컴파일 오류
  • 추상 클래스의 포인터 변수나 레퍼런스 변수는 정의할 수 있다.
    • 파생 클래스 객체를 가리키는 용도로 사용
    • 파생 클래스 객체에 접근하는 인터페이스 역할 제공
Shape *p;

 

추상 클래스의 목적

추상 클래스의 목적

  • 추상 클래스의 인스턴스를 생성할 목적이 아님
  • 상속에서 기본 클래스의 역할을 하기 위함
    • 순수 가상 함수를 통해 파생 클래스에서 구현할 함수의 형태(원형)을 보여주는 인터페이스 역할
    • 추상 클래스의 모든 멤버 함수를 순수 가상 함수로 선언할 필요 없음

 

추상 클래스의 상속과 구현

추상 클래스의 상속

  • 추상 클래스를 단순 상속하면 자동 추상 클래스

추상 클래스의 구현

  • 추상 클래스를 상속받아 순수 가상 함수를  오버라이딩
    • 파생 클래스는 추상 클래스가 아님

 

Shape을 추상 클래스로 수정

 

구현 상속과 인터페이스 상속

구현상속

  • 기본 클래스가 해당 함수의 구현을 제공
  • 클래스의 멤버 함수가 일반 함수라면 파생 클래스에서는 이 함수를 재정의할 필요가 없다.

 

디폴트 구현 상속 + 인터페이스 상속

  • 파생 클래스가 해당 함수를 재정의할지 여부 선택
  • 클래스의 멤버 함수가 가상 함수라면 파생 클래스에서는 이 함수를 재정의할 수도 있고, 재정의하지 않을 수도 있다.

인터페이스 상속

  • 클래스의 멤버 함수가 순수 가상 함수라면 파생 클래스에서는 이 함수를 반드시 재정의해야 한다.

 

다양한 종류의 멤버 함수

상속곽 관련해서 지금까지 살펴본 멤버 함수 종류

  • 일반적인 멤버 함수
  • 가상 함수
  • 순수 가상 함수

 

어떤 종류의 멤버 함수를 사용할 지에 대한 가이드 라인

  • 처음엔 그냥 멤버 함수로 만든다.
  • 다형성을 이용해야 하는 경우라면 가상 함수로 만든다.
  • 다형성을 위해서 함수의 원형만 필요한 경우라면 순수 가상 함수로 만든다.

'공부 > C++' 카테고리의 다른 글

12. 템플릿과 표준 템플릿 라이브러리(STL)  (0) 2024.06.06
10. 상속  (0) 2024.05.27
9. friend와 연산자 중복  (0) 2024.05.16
8. 함수와 참조, 복사생성자  (1) 2024.05.14
7. 여러가지 객체의 생성방법  (0) 2024.05.14