Programming Tools/C++

[코드 중심 C++] - Polymorphism(다형성), 추상 클래스(Abstract Class), virtual, 순수 가상 함수(Pure Virtual Function)

LiDARian 2021. 5. 16. 16:00
반응형

1

여러 개의 서로 다른 객체가 동일한 기능을 서로 다른 방법으로 처리할 수 있는 기능을 다형성이라고한다. 예를 들면 필기구 역할을 하는 다양한 도구들 - 볼펜, 만년필, 연필- 은 서로다른 객체지만, 문서 작성이라는 동일한 기능을 수행한다. 간단히 말하면, 같은 클래스에서 나온 서로다른 객체가, 같은 메소드 호출에 대해서 서로 다른 출력을 내놓는다는 얘기다. 그런데 메소드는 기본적으로 단 하나의 메모리에서 모든 객체가 공유하게되는데, 이게 어떻게 가능한가?
그래서 우린 추상 클래스와 가상함수의 개념을 사용한다.


객체 포인터 자료형의 변수로 포인터에 접근할 때에는 아래와같은 문제가 발생할 가능성이 높다. (& 자료형으로 정의해도 마찬가지다.)
아래의 예시의 경우, C++ 컴파일러는 가리키고 있는 변수의 타입을 기준으로 함수를 호출하지 않고, 포인터의 타입을 기준으로 함수를 호출한다. 따라서 A클래스 포인터 변수는 A 객체의 멤버 함수만을 호출할 수 있다.
이런 문제를 정적 바인딩이라고 한다.

#include <iostream>

using namespace std;

class A
{
public:
    void show(){cout << "A클래스" << endl;}
};

//B는 A를 상속
class B : public A
{
public:
    void show(){ cout << "B클래스" << endl;}
};


int main()
{
    A* p;
    A a;
    B b;

    p = &a;
    p -> show();
    p = &b;
    p -> show();    //b를 통해서 show()호출 되는 것이 아니라, A class를 통해서 show()가 호출된다.
    a.show();
    b.show();
}

// output
// A클래스
// A클래스
// A클래스
// B클래스

이와 같은 문제를 정적바인딩이라고 한다.

 

2

이를 위해서 각 객체를 찍어내는데 쓰이는 클래스를 추상클래스(Abstract Class)로 두고 공통으로 쓰이는 함수를 가상함수(Virtual Function)이라고 부른다.
그 관계를 표현하자면 아래와 같다.

출처 : https://pacs.tistory.com/entry/C-상속과-다형성-Inheritance-Polymorphism

추상 클래스(Abstract Class)란 하나 이상의 순수 가상 함수를 포함하는 클래스를 의미한다. 추상 클래스가 동적바인딩으로 바꿔줘서 정적 바인딩 문제를 해결한다. 그리고 가상 함수는 부모 클래스에서 함수 동작의 본체를 정의하지 않는다. 자식 클래스에서 재정의해야 사용할 수 있다.

그림과 같은 출처에서의 코드를 보면,

#include <iostream>
#include <string>
using namespace std;

class Figure
{
public:
    virtual string draw() = 0;    // 가상함수의 정의
};

class Triangle : public Figure
{
public:
    virtual string draw() { return "Draw Triangle"; }
};

class Square : public Figure
{
public:
    virtual string draw() { return "Draw Square"; }
};

class Circle : public Figure
{
public:
    virtual string draw() { return "Draw Circle"; }
};

int main()
{
    Figure* F1 = new Triangle;
    Figure* F2 = new Square;
    Figure* F3 = new Circle;

    cout << F1->draw() <<endl;
    cout << F2->draw() <<endl;
    cout << F3->draw() <<endl;

    delete F1;
    delete F2;
    delete F3;

    return 0;
}

위 코드에서 Figure클래스가 superclass이자 abstract class이고, 그 superclass에게서 상속을 한 childclass Triangle, Square, Circle은 다시 virtural function을 다시 정의한다..

출력은 이와같다.

 

 

3

#include <iostream>

using namespace std;

class A
{
public:
    virtual void show(){cout << "A클래스" << endl;}
};

//B는 A를 상속
class B : public A
{
    virtual void show(){ cout << "B클래스" << endl;}
};


int main()
{
    A* p;
    A a;
    B b;

    p = &a;
    p -> show();
    p = &b;
    p -> show();
    a.show();
}

 

4

순수 가상 함수를 모두 오버라이딩 해야 비로소 해당 객체를 사용할 수 있다.
순수 가상 함수(Pure Virtual Function)는 자식 클래스에서 반드시 재정의를 해주어야 하는 함수다.
순수 가상 함수는 부모 클래스에서 함수 동작의 본체를 정의하지 않는다. 자식 클래스에서 재정의해야 사용할 수 있다.
순수 가상 함수는 '=0'을 붙여서 선언한다.

설명을 인용하자면

A pure virtual function (or abstract function) in C++ is a virtual function for which we can have implementation, but we must override that function in the derived class, otherwise the derived class will also become abstract class.
(derived class에서 pure virtual function을 오버라이딩 안하면 derived class는 그저 abstract class가 된다.)

We cannot create objects of abstract classes.
(추상클래스는 객체 생성이 불가능하다.)

 

#include <iostream>

using namespace std;

class A
{
public:
    //pure-specifier on function-definition : virtual void show()=0 { cout << "A클래스" << endl;}
    virtual void show()=0;    //definition에서는 순수 가상함수 선언 불가.
};

void A::show() { cout << "A클래스" << endl;}

//B는 A를 상속
class B : public A
{
public:
    virtual void show(){ cout << "B클래스" << endl;}    //show()를 재정의하지 않으면 B클래스의 객체를 사용할 수 없다.
};


int main()
{
    A* p;
    B b;

    p = &b;
    p -> show();
}

 


추상클래스는 객체 생성이 불가능하다.

#include<iostream>
using namespace std;
class Base
{
public:
    virtual void show() = 0;
};

class Derived : public Base { };

int main(void)
{
  Derived d;
  return 0;
}


더 자세하게

Rules for Virtual Functions

  1. Virtual functions cannot be static.
  2. A virtual function can be a friend function of another class.
  3. Virtual functions should be accessed using pointer or reference of base class type to achieve run time polymorphism.
  4. The prototype of virtual functions should be the same in the base as well as derived class.
  5. They are always defined in the base class and overridden in a derived class. It is not mandatory for the derived class to
  6. override (or re-define the virtual function), in that case, the base class version of the function is used.
  7. A class may have virtual destructor but it cannot have a virtual constructor.

If we have created a virtual function in the base class and it is being overridden in the derived class then we don’t need virtual keyword in the derived class, functions are automatically considered as virtual functions in the derived class.
(superclass에서 virtual 키워드 붙였으면 derived class에서 굳이 virtural 붙일 필요는 없다는 뜻이다. 그냥 재정의만 하면 된다.)

If a class contains a virtual function then compiler itself does two things:
If object of that class is created then a virtual pointer(VPTR) is inserted as a data member of the class to point to VTABLE of that class. For each new object created, a new virtual pointer is inserted as a data member of that class.
Irrespective of object is created or not, a static array of function pointer called VTABLE where each cell contains the address of each virtual function contained in that class.


참고 : 다른 다양한 예시를 보면서 감을 익히자.

C++ 상속과 다형성

Virtual Function in C++

정적바인딩 동적바인딩

바인딩이란?

반응형