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 |
---|
변수 사용 여부 | 사용 가능 | 사용 불가능 |
메소드 사용 가능 여부 | 일반 메소드 포함 가능 | 추상 메소드만 포함 가능 |
다중상속 가능 여부 | 불가능(단일상속) | 가능 |
클래스 구성 요소 | 일반변수+일반메소드+추상메소드(하나 이상 필수) | 오직 추상 메소드만 가능 |
공통점
- 추상 메소드를 포함하고 있다.
- 인스턴스를 생성할 수 없다(객체화 할 수 없다).
- 따라서 추상클래스나 인터페이스를 물려받은 클래스의 인스턴스를 사용해야한다.
- 추상 메소드를 반드시 구현해야한다.
목적
- 추상클래스는 하위 클래스들의 공통점들이 모여 추상화되어 만들어진 클래스이다.
- 추상클래스는 클래스간의 논리적 연관 관계를 생성하는 것에 목적이 크다.
- 추상클래스는 자신의 기능들을 하위 클래스로 확장시키는 개념.
- 추상클래스는 클래스간 명확한 계층 또는 논리적 관계를 나타낼때 추상클래스를 사용한다.
- 논리적 관계의 몇가지 예시는 아래와 같다.
- 원, 삼각형, 사각형 -> 상위 카테고리인 도형 클래스.
- 강아지, 고양이, 앵무새 -> 상위 카테고리인 동물 클래스.
- 에어팟, 맥북, 아이폰 -> 상위 카테고리인 애플 클래스.
- 인터페이스는 부모 자식 관계의 논리적인 관계보단, 공통 기능이 필요할 때 탈부착 가능한 개념으로 추상 메소드를 구현하며 상위 클래스인 인터페이스를 생성한다.
- 인터페이스에 명시된 메소드를 파생클래스에서 따로 구현해 각 클래스의 목적에 맞게 사용하는 개념.
- 인터페이스는 서로 관련성이 없는(논리적 관계가 형성되어 있지 않은) 클래스를 추상화 할때 사용할 수 있다.
- 예를들어, 여러 종류의 동물들이 있을 때, 동물들이 구현해야 하는 공통된 기능들을 인터페이스로 정의할 수 있습니다. 이 때 인터페이스에는 동물이 가지고 있어야 할 공통된 기능(메소드)을 추상 메소드(순수 가상 함수)로 정의하고 파생클래스에서 해당 메소드를 구현합니다. 이렇게 인터페이스를 사용하면 다른 클래스들 간의 논리적인 관계가 형성되어 있지 않아도 공통된 동작을 지원할 수 있습니다.
- 인터페이스는 다중상속이 필요할 때 사용할 수 있습니다.