프로그래밍 언어/C++

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

busy맨 2023. 12. 9. 01:18

1.  가상 함수

1) 가상 함수(virtual function)

  • virtual 키워드로 선언된 멤버 함수
    • 동적 바인딩 지시어
    • 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시

  • 함수 오버라이딩(function overriding)
    • 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언
    • 파생 클래스에서 오버라이딩한 함수가 호출되도록 동적 바인딩
    • 기본 클래스의 가상함수를 실행 X

오버로딩 vs 오버라이딩

 

  • 오버라이딩과 가상 함수 호출
#include <iostream>
using namespace std;

class Base {
public:
	virtual void f() { cout << "Base::f() called" << endl; }
};
class Derived : public Base {
public:
	virtual void f() { cout << "Derived::f() called" << endl; }
};
int main() 
{
	Derived d, * pDer;
	pDer = &d;
	pDer->f(); // Derived::f() 호출
	Base* pBase;
	pBase = pDer; // 업 캐스팅
	pBase->f(); // 동적 바인딩 발생!! Derived::f() 실행
}

 

2) 동적 바인딩

  • 파생 클래스에 대해, 기본 클래스에 대한 포인터로 가상 함수를 호출하는 경우
  • 객체 내에 오버라이딩한 파생 클래스의 함수를 찾아 실행
    • 실행 중에 이루어짐

3) 오버라이딩 성공 조건

  • 가상 함수 이름, 매개 변수 타입과 개수, 리턴 타입이 모두 일치
  • 오버라이딩 시 virtual 키워드 생략 가능
class Base {
public:
virtual void fail();
virtual void success();
virtual void g(int);
};

class Derived : public Base {
public:
virtual int fail(); // 오버라이딩 실패. 리턴 타입이 다름
virtual void success(); // 오버라이딩 성공
virtual void g(int, double); // 오버로딩 사례. 정상 컴파일
};

 

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

  • 정적 바인딩 지시
  • 기본클래스::가상함수() 형태로 기본 클래스의 가상 함수를 정적 바인딩으로 호출
class Shape {
public:
	virtual void draw() {
	...}
};

class Circle : public Shape {
public:
    virtual void draw() {
    Shape::draw(); // 기본 클래스의 draw()를 실행한다.
    .... // 기능을 추가한다.
	}
};

 

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

#include <iostream>
using namespace std;
class Shape {
public:
	virtual void draw() {
		cout << "--Shape--";
	}
};

class Circle : public Shape {
public:
	virtual void draw() {
		Shape::draw(); // 기본 클래스의 draw() 호출
		cout << "Circle" << endl;
	}
};
int main() {
	Circle circle;
	Shape* pShape = &circle;
	pShape->draw();	//동적 바인딩
	pShape->Shape::draw();	//정적 바인딩, Shape의 draw()로 지정
}

 

5) 가상 소멸자

  • 소멸자를 virtual 키워드로 선언
  • 소멸자 호출 시 동적 바인딩 발생
  • 클래스에 가상함수가 있고, 클래스가 반드시 소멸자를 사용해야할 때 선언해줘야함

 

Ex) 소멸자를 가상함수로 선언

#include <iostream>
using namespace std;

class Base {
public:
	virtual ~Base() { cout << "~Base()" << endl; }
};

class Derived : public Base {
public:
	virtual ~Derived() { cout << "~Derived()" << endl; }
};

int main() {
	Derived* dp = new Derived();
	Base* bp = new Derived();
	delete dp; // Derived의 포인터로 소멸
	delete bp; // Base의 포인터로 소멸
}

 

2. 추상클래스와 인터페이스 상속

1) 순수 가상함수

  • 기본 클래스의 가상 함수 목적
    • 파생 클래스에서 재정의할 함수를 알려주는 역할
  • 순수 가상함수
    • 함수의 코드가 없고, 선언만 있는 가상 멤버 함수
    • 파생 클래스에서 오버라이딩될 함수에 대해 원형이 필요하지만, 기본 클래스에서 정의할 필요가 없을 때 사용
    • 파생 클래스는 상속받은 순수 가상함수를 반드시 재정의해야함
  • 순수 가상함수 선언 방법
    • 함수 선언의 끝 부분에 "=0"을 적어둥

 

2) 추상 클래스(abstract class)

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

void Shape::paint() {
	draw(); // 순수 가상 함수라도 호출은 할 수 있다.
}
  • 특징
    • 온전한 클래스가 아니므로 추상 클래스는 객체를 생성할 수 없음
    • 추상 클래스의 포인터 변수나 레퍼런스 변수는 정의 가능
      • 파생 클래스 객체를 가리키는 용도
      • 파생 클래스 객체에 접근하는 인터페이스 역할
  • 목적
    • 상속에서 기본 클래스의 역할을 하기 위함
      • 순수 가상함수를 통해 파생 클래스에서 구현할 함수의 원형을 보여주는 인터페이스 역할
      • 추상 클래스의 모든 멤버 함수를 가상 함수를 선언할 필요 X

'프로그래밍 언어 > C++' 카테고리의 다른 글

12) 템플릿  (1) 2023.12.09
10) 상속  (0) 2023.12.09
9) friend와 연산자 중복  (0) 2023.12.08
8) 함수와 참조, 복사생성자  (0) 2023.12.08
7) 여러가지 객체의 생성 방법  (1) 2023.11.03