C++ 가상함수, 순수가상함수
Pure virtual function(순수 가상함수, 추상메소드): 파생 클래스에서 반드시 재정의해야 하는 함수를 말합니다.
- 일반적으로 함수의 동작을 정의하는 본체를 가지고 있지 않으며 따라서, 파생 클래스에서 재정의하지 않으면 사용할 수도 없습니다.
- 이는 Java의
abstract 메소드와 동일합니다.
virtual funciton(가상함수): 파생 클래스에서 재정의할 것으로 기대되는 함수입니다.
- 확실히 알아야 할 점은, 반드시 재정의해야 하는 함수가 아니라 재정의가 가능한 함수를 말합니다.
class MyBaseClass {
public:
virtual void doSomething() = 0;
virtual void doSomethingElse() {
}
};
Abstract class (추상 클래스)
- Abstract class(추상 클래스)란 하나 이상의 순수 가상 함수를 포함하는 클래스를 추상클래스라고 하며, 이 클래스로부터 파생된 모든 클래스에서는 이 가상 함수를 반드시 재정의해야 한다.
- 따라서 abstract class(추상클래스)는 객체화 할 수 없다.
- 동작이 정의되지 않은 순수 가상 함수를 가지고 있기 때문.
- 상속을 통해 파생 클래스를 만든 후, 파생 클래스에서 순수 가상 함수를 모두 오버라이딩 한 후에 해당 클래스에서 인스턴스를 생성할 수 있다.
- 또한 추상클래스는 상태나 구현을 포함할 수 있다.
- 일반 변수나 일반 함수를 가질 수 있다. 하지만 하나 이상의 추상메소드를 포함해야 한다.
- 추상클래스의 목적은 파생클래스가 그 기능을 상속받아 이용하고 확장시키는데 있다.
- 추상 클래스는 단일 상속만 가능하다.
Interface (인터페이스)
- C++에서 인터페이스는 사실 공식적으로 지원하지 않아 간접적으로 구현하는 방법을 사용하며 따라서 인터페이스로 사용한다는 것을 알리기 위해 인터페이스의 이름은 항상
I로 시작하도록 약속되어있다.
xxxable로 이름을 하는 경우도 있는데 이건 Java에서만 그런지 모두 그런지는 모르겠다.
- 인터페이스란 모든 메소드가 추상 메소드(
pure virtual function)인 경우를 말합니다.
- 따라서 인터페이스 클래스에는 구현부가 존재하지 않는다.
- interface 역시 인스턴스를 생성할 수 없다(객체화 할 수 없다).
- 동작이 정의되지 않은 추상 메소드를 포함하고 있기 때문.
- abstract class 와 마찬가지로 파생 클래스에서 인스턴스 생성 가능.
- interface 는 상태나 구현을 포함할 수 없다.
- interface 는 상속받은 구현 객체가 같은 동작을 하는것을 목적으로 한다(같은 이름의 메소드를 사용하는것을 목적으로 한다).
- 인터페이스는 다중 상속이 가능하다.
#include <iostream>
using namespace std;
class Camera {
public:
void take() {
cout<<"Take a picture with a camera."<<endl;
}
}
class Person {
public:
void useCamera(Camera* p) {
p->take();
}
}
- 하지만 여기서 더 좋은 카메라가 나타난다면 우리는 기존에 존재하던
Camera클래스를 수정해야한다.
- 이는 SOLID의 open close principle(개방 폐쇄의 법칙)에 위배된다.
- 기능확장(모듈, 클래스, 함수 추가)에 열려있고 기존 코드 수정에는 닫혀있음.
- 따라서 더 괜찮은 카메라가 나타나도 기존의 코드를 수정하는 일이 일어나선 안된다.
- 아래와 같이 인터페이스를 장착하면 위의 문제점을 해결할 수 있다.
#include <iostream>
using namespace std;
struct ICamera
{
virtual void take() = 0;
};
class People
{
public:
void useCamera(ICamera* p) { p->take();}
};
class Camera : public ICamera
{
public:
void take()
{
cout<<"Take a picture."<<endl;
}
};
class BetterCamera : public ICamera
{
public:
void take()
{
cout<<"Take a better picture."<<endl;
}
};
int main()
{
People p;
Camera c1;
p.useCamera(&c1);
BetterCamera bc;
p.useCamera(&bc);
return 0;
}
- 위처럼 인터페이스를 통해 얻을수 있는 장점은
People의 인스턴스인 p는 ICamera의 take 함수를 사용해 어떤 카메라를 사용하든 주입받는 카메라를 같은 함수로 사용할 수 있다.
- 나중에
BetterCamera 보다 더 좋은 BestCamera가 나타난다 하더라도 p는 같은 메소드인 take를 사용하면 인터페이스를 통해 BestCamera의 카메라를 사용할 수 있다.
- 인터페이스의 목적은 함수의 껍데기만 있으며 해당 함수의 구현을 파생 클래스에서 강제로 수행하도록 하기 위함이다.
abstract VS interface 차이점 및 공통점
- 추상클래스와 인터페이스는 사실 비슷하면서도 다른부분이 존재한다.
차이점
| abstract | interface |
|---|
| 변수 사용 여부 | 사용 가능 | 사용 불가능 |
| 메소드 사용 가능 여부 | 일반 메소드 포함 가능 | 추상 메소드만 포함 가능 |
| 다중상속 가능 여부 | 불가능(단일상속) | 가능 |
| 클래스 구성 요소 | 일반변수+일반메소드+추상메소드(하나 이상 필수) | 오직 추상 메소드만 가능 |
공통점
- 추상 메소드를 포함하고 있다.
- 인스턴스를 생성할 수 없다(객체화 할 수 없다).
- 따라서 추상클래스나 인터페이스를 물려받은 클래스의 인스턴스를 사용해야한다.
- 추상 메소드를 반드시 구현해야한다.
목적
- 추상클래스는 하위 클래스들의 공통점들이 모여 추상화되어 만들어진 클래스이다.
- 추상클래스는 클래스간의 논리적 연관 관계를 생성하는 것에 목적이 크다.
- 추상클래스는 자신의 기능들을 하위 클래스로 확장시키는 개념.
- 추상클래스는 클래스간 명확한 계층 또는 논리적 관계를 나타낼때 추상클래스를 사용한다.
- 논리적 관계의 몇가지 예시는 아래와 같다.
- 원, 삼각형, 사각형 -> 상위 카테고리인 도형 클래스.
- 강아지, 고양이, 앵무새 -> 상위 카테고리인 동물 클래스.
- 에어팟, 맥북, 아이폰 -> 상위 카테고리인 애플 클래스.
- 인터페이스는 부모 자식 관계의 논리적인 관계보단, 공통 기능이 필요할 때 탈부착 가능한 개념으로 추상 메소드를 구현하며 상위 클래스인 인터페이스를 생성한다.
- 인터페이스에 명시된 메소드를 파생클래스에서 따로 구현해 각 클래스의 목적에 맞게 사용하는 개념.
- 인터페이스는 서로 관련성이 없는(논리적 관계가 형성되어 있지 않은) 클래스를 추상화 할때 사용할 수 있다.
- 예를들어, 여러 종류의 동물들이 있을 때, 동물들이 구현해야 하는 공통된 기능들을 인터페이스로 정의할 수 있습니다. 이 때 인터페이스에는 동물이 가지고 있어야 할 공통된 기능(메소드)을 추상 메소드(순수 가상 함수)로 정의하고 파생클래스에서 해당 메소드를 구현합니다. 이렇게 인터페이스를 사용하면 다른 클래스들 간의 논리적인 관계가 형성되어 있지 않아도 공통된 동작을 지원할 수 있습니다.
- 인터페이스는 다중상속이 필요할 때 사용할 수 있습니다.