프로그래밍/C++

C++ 순수 가상 함수란? 추상 클래스란??(pure virtual function, abstract class)

Hwan2 2020. 6. 20. 15:09
반응형

1. 순수 가상 함수란?(pure virtual function)


C++에서의 순수 가상 함수란 무엇일까요??


virtual void foo() = 0;


이렇게 생긴 녀석들을 순수 가상함수라고 부릅니다. (pure virtual function)


함수의 정의가 이뤄지지 않고 함수만 선언한 것이지요.


이렇게 선언된 순수 가상 함수가 있다면 이를 추상클래스(abstract class)라고 부릅니다.


또한 이 추상클래스는 객체로 만들지 못하고 상속으로써만 사용됩니다. 


그리고 추상클래스를 상속받은 자식 클래스는 무조건 해당 순수 가상 함수를 override 시켜줘야 합니다.


이 추상클래스, 순수 가상 함수가 어떤 이점이 있으며, 어느 상황에 사용하는지 설명함에 앞서


함수 객체의 생성이 안되는지부터 확인해보겠습니다.


1) 안되는 예제 코드 1

#include <iostream>

class A {
public:
    virtual void foo() = 0;
};

class B : public A {

};

int main(void) {

    A a;    //error
    B b;    //error
    return 0;
}

설명

A클래스는 추상클래스(abstract class)이므로 객체 선언이 불가능 합니다.
B클래스는 추상클래스를 상속받기 때문에 virtual void foo() = 0; 를 override 해줘야 합니다.


2) 안되는 예제 코드2

#include <iostream>

class A {
public:
    virtual void foo() = 0;
    void print() {
        printf("hi\n");
    }
private:
    int num = 0;
};

int main(void) {

    A a;    //error

    return 0;
}

설명

A클래스를 저런식으로 함수를 정의해주고 맴버변수를 선언해 줘도 순수 가상 함수(pure virtual function)이 존재하기 때문에
객체 선언이 안됩니다. 이로써 순수 가상 함수가 선언된 클래스는 추상 클래스(abstract class)로 바뀌는게 증명이 된 것입니다.

3) 되는 예제 코드2

#include <iostream>

class A {
public:
    virtual void foo() = 0;
};

class B : public A {
public:
    void foo() override {
        printf("오버라이드~\n");
    }
};

int main(void) {

 //   A a;
    B b;
    b.foo();    //output : 오버라이드~
    return 0;
}

설명

A클래스는 추상클래스(abstract class)기 때문에 주석처리를 해줬습니다.
B클래스는 추상클래스를 상속받기 때문에 순수 가상함수인 foo()를 override 해줬습니다.
이렇게 해주면 정상 작동이 됩니다.



2. 그럼 어떤 상황에 사용될까?

순수 가상 함수(pure virtual function)를 선언한 클래스는 추상 클래스(abstract class)가 된다는 사실을 알았습니다.
그럼 추상 클래스의 추상은 무엇을 의미 할까요??

말 그대로 추상입니다. 상상이라고 생각하면 되겠습니다.
코드를 상상한다?? 잘 이해가 안갑니다.

예를 들어 동물 이라는 클래스를 정의하고 싶습니다. 상속을 통해서.....
동물은 사람, 개, 코끼리, 돌고래, 새 등..... 여러가지가 있습니다.
이런 동물들에게는 공통적인 특징도 있고, 서로 다른 특징도 있습니다.

공통된 특징은 무엇일까요??
간단하게 생각(상상)해 본다면 다음과 같습니다.
1. 말을한다.
2. 눈이 있다.
3. 호흡을 한다.
4. 피를 갖고 있다.
등......
생각나는대로 적어봤는데, 공통된 요소는 더 있을 것입니다.

그럼 서로 다른점에 대해 생각(상상)해 보겠습니다.
1. 다리가 있는 동물도 있고, 없는 동물도 있다.
2. 손이 있는 동물이 있고, 손이 없는 동물이 있다.
3. 알을 낳는 동물이 있고, 새끼를 낳는 동물이 있다.
4. 물에서 사는 동물이 있고, 땅위에 사는 동물이 있다.
등....
여러가지 다른점들이 있습니다.


그럼 여기서 공통된 특징에 대해 좀 더 살펴보겠습니다.
1. 말을한다.
사람은 말을 합니다. 하지만 새는 지저귑니다. 돌고래는? 고주파로 소통합니다.
말을 하는건 같지만 서로 대화하는 방식은 다릅니다.

2. 눈이 있다.
동물들은 눈이 있죠. 하지만 고양이눈과 사람눈이 같나요?
새눈과 사람눈이 같나요? 다르죠. 사람은 흰 눈동자와 검은 눈동자가 있고, 동그랗습니다.
고양이는? 세로로 날카롭죠. 세는? 흰 눈동자가 없습니다.

3. 호흡을 한다.
땅위에 사는 동물들은 산소로 호흡을 하죠.
하지만 바다에 사는 동물들은? 산소로 호흡을 하기도 하지만 물에서도 호흡을 합니다.

4. 피를 갖고 있다.
피를 갖고 있죠... 빨갛죠.... 음.... 차이점이 없네요?....
굳이 따지자면..... 혈액 속에 포함되어 있는 여러가지 세포가 다르겠죠??(너무 갔나...)


여튼!! 공통된 요소라도 차이점이 존재합니다.
이를 코드를 통해 클래스로 정의해본다고 가정해 봅시다.

만약에 상속을 받지 않고 각 클래스별로 정의한다면??
저희는 일일이 다 적어줘야합니다.(상속해도 일일이 정의해줘야 하지만 다음 문장에 차이가 있습니다.)
다만!! 그렇게 될 경우 몇가지 공통된 점을 빠뜨릴 수 있는 실수를 범할 수 있습니다.
예를 들어 고양이 클래스는 완벽하게 정의를 했는데....
사람, 돌고래 클래스는 한 두가지 정의가 빠질 수 있다는 겁니다.
(데이터가 2~3개라면 상관없지만 10개가 넘어가면?? 분명히 실수할 수 있습니다.

추상 클래스의 특징이 뭐죠?? override 즉, 함수를 재정의 해 주지 않는다면 코드상에서 오류로 판단합니다.
떄문에 이런 실수들을 방지할 수 있죠.

또한 공통된 요소라 할 지라도 안의 함수 내용은 달라질 수 있습니다.
예를들어 talk라는 순수 가상 함수를 override했다고 가정했을 때, 
강아지라면 "멍멍!"으로 정의해야 하고, 새라면 "짹짹"이라고 정의해야 합니다.

만약 순수 가상 함수가 아닌 일반적인 가상 함수라면?
일반적인 가상 함수로 정의했다면??
물론, 가상 함수를 override해 재정의가 가능합니다.
하지만 미처 정의하지 못한 함수(위에서 말한 실수로 빼먹은 함수들)들이 있을 수 있습니다.
하지만 코드상 문제없이 실행되겠죠. (왜? 순수 가상 함수는 무조건 재 정의를 해줘야 하지만 일반 가상함수는 재 정의를 하지 않아도 되기 때문에 프로그램상에서 오류로 보지 않습니다.)
그렇게 되면 몇가지 빼먹은 기능이 있는 프로그램이 될 수 있는 것입니다.


3. 바람직한 추상 클래스(abstract class) = interface

추상클래스를 정의할 때 맴버변수 없이 순수하게 순수 가상 함수로만 이뤄진 클래스가 좋습니다.


예제 코드를 보겠습니다.

class A {
public:
    virtual void foo() = 0;
    virtual void talk() = 0;
    virtual int number() = 0;
};

class B {
    B(string s) { name = s; }
    virtual void foo() = 0;
    void print() {
        printf("hi\n");
    }
private:
    int arr[10];
    int num = 0;
    string name;
};

설명

A클래스는 순수 가상 함수로 이뤄진 추상 클래스입니다.

B클래스는 순수 가상 함수와 맴버변수, 일반적인 함수로 이뤄진 추상 클래스 입니다.


A를 상속받게 된다면 필요해 의해 함수들을 모두 재 정의해야 합니다.

때문에 위에서 말씀 드린것 처럼 함수 정의를 빼먹는 실수를 없엘 수 있고, 상속으로 인해 차지하는

불필요한 데이터들도 없습니다.


B를 상속받게 된다면 해당 맴버변수들의 데이터 크기까지 자식 클래스가 갖게 됩니다.

하지만 자식 클래스에서 B클래스의 맴버변수를 사용 안할 수 있고, 함수도 사용 안할 수 있습니다.

그렇게 된다면 이는 불필요한 데이터가 되겠죠?

그럼 이렇게 말씀하시는 분들도 계실 겁니다. 불필요하면 지우면 되지 않느냐?

만약에 B클래스의 자식들이 여러게 있는데, 여러개의 자식들 중 몇개는 맴버변수를 사용하고,

몇개의 자식들은 사용하지 않는다면?

관리가 복잡해지고 머리가 아파집니다. 


때문에 추상 클래스는 저렇게 순수 가상 함수로만 이뤄진 클래스가 좋습니다.!!

그리고 순수 가상 함수로만 이뤄진 추상 클래스를 인터페이스(interface)라고 부릅니다.


4. 그럼 인터페이스 + 추상 클래스의 기능을 사용하려면?

C++에서는 다중상속이란 개념이 존재합니다.

때문에 위 제목처럼 사용하고 싶다면

인터페이스 클래스를 하나 만들어 놓고, 그 외에 implementation한 클래스(구현이 완료된 일반적인 클래스)를

다중 상속을 통해 같이 사용하게 된다면 해결 할 수 있을 것입니다.




반응형