네이버 이웃 추가 / GitHub Profile / 카카오톡 채널 추가 / 방명록 / 이용 안내

C++ Polymorphism(overloading, overriding)

수성컴 | 2023. 12. 22.
목차
C++ Polymorphism(overloading, overriding)

수성컴전자방입니다. 오늘은 C++의 function overloading, operator overloading, overriding을 글 하나로 정리하겠습니다. 이것들처럼 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질을 polymorphism(다형성)이라 합니다. overloading은 static polymorphism이고, overriding은 dynamic polymorphism입니다.

목차

1. Function Overloading

2. Operator Overloading
2.1. 기본 규칙
2.2. Binary Operator(+, - 등) Overloading
2.3. 서로 다른 자료형 간의 교환법칙(ex. 벡터의 실수배)
2.4. Unary Operator(단항연산자) Overloading
2.5. 대입 연산자(=) Overloading
2.6. 전위 증감 연산자(++v1, --v1) Overloading
2.7. 후위 증감 연산자(v1++, v1--) Overloading
2.8. 비교 연산자(<, <=, >=, >, ==, != 등) Overloading
2.9. ostream(<<) Overloading
2.10. istream(>>) Overloading
2.11. 완성 예제 코드

3. Overriding
3.1. 배경 설명(virtual function을 왜 쓰는가?)
3.2. Virtual Function
3.3. destructor(소멸자)는 virtual function으로 구현하는 것이 좋다.
3.4. Pure Virtual Function
3.5. 활용

4. 최종 정리 및 글 마무리
5. 참고 자료

1. Function Overloading

return type이나 parmeter 개수, parameter type을 다르게 하여 같은 이름의 함수를 여러 개 만드는 것을 function overloading이라고 합니다. 예제를 보겠습니다.

#include <iostream>
using namespace std;

void printType(int a);
void printType(int a, int b);
void printType(double a);
void printType(double a, double b);

int main(void)
{
    printType(5);
    printType(1, 2);
    printType(5.2);
    printType(1.1, 1.2);

    return 0;
}

void printType(int a){
    cout << a << " is int variable." << endl;
}

void printType(int a, int b){
    cout << a << " and " << b << " are int variables." << endl;
}

void printType(double a){
    cout << a << " is double variable." << endl;
}

void printType(double a, double b){
    cout << a << " and " << b << " are double variables." << endl;
}

4~7행은 함수의 prototype이고, 19~33행은 함수의 정의입니다. 이름은 같지만 parmeter 개수와 parameter type이 다른 4개의 함수를 만들었습니다.
11행 printType(5);은 parameter가 int형 5 하나뿐이므로 void printType(int a)가 호출됩니다.
12행 printType(1, 2);은 parameter가 int형 1, 2 둘이므로 void printType(int a, int b)가 호출됩니다.
13행 printType(5.2);은 parameter가 double형 5.2 하나뿐이므로 void printType(double a)가 호출됩니다.
14행 printType(1.1, 1.2);은 parameter가 double형 1.1, 1.2 둘이므로 void printType(double a, double b)가 실행됩니다.

function-overloading
컴파일하고 실행하면 위와 같은 결과가 나옵니다.

2. Operator Overloading

C++에서 원하는 class를 만들어 사용할 수 있습니다. 만약 직접 만든 class의 object끼리 연산하기 위해서는 operator overloading을 해야 합니다.

예제로 고등학교 기하 시간에 배우는 평면벡터를 class로 구현해 보겠습니다(STL의 vector가 아닙니다).

//...
class vt{
    private:
        double x;
        double y;
    public:
        vt(double a=0, double b=0){ init(a, b); }
        ~vt(){}
        void init(double a, double b){ x=a; y=b;}
};
//...

‘vt’라는 이름의 class를 만들었습니다. 물론 class 외에 전처리(#include)도 넣어야 하고 main 함수도 작성해야 할 것입니다. 그런 것이 다 있다고 치면 vt v1(1, 2);와 같은 방법으로 벡터를 선언할 수 있습니다. 그러나 v1+v2 같은 연산은 아직 할 수 없는데, 이어지는 내용에서 연산을 구현해 보도록 하겠습니다.

2.1. 기본 규칙

  • 함수 이름은 operator연산자입니다. 예) +를 overloading하려면 operator+ 선언
  • 함수의 parameter
    • member function 이용 시: 연산자 오른쪽에 있는 operand만 포함
    • non-member일 경우: 연산자 왼쪽과 오른쪽에 있는 operand 모두 포함
  • overloading 가능한 연산자: +, -, *, /, %, ^, &, |, ~, !, =, <, >, +=, -=, *=, /=, %=, ^=, &=, |=, <<, >>, >>=, >>==, <<=, ==, !=, <=, >=, &&, ||, ++, --, ->*, , ->, [], (), new, delete, new[], delete[]
  • overloading 불가능한 연산자: ., .*, ::, ?:, sizeof, #, ##
  • 연산자 우선순위(precedence)는 바꿀 수 없습니다.
  • 연산자 결합 순위(associativity)는 바꿀 수 없습니다.
  • operand 개수를 바꿀 수 없습니다. 예) 1 + 2 3 불가
  • &, *, +, -는 unary로써의 기능과 binary로써의 기능이 각각 존재합니다. 따로 구현할 수 있습니다.
  • 새로운 연산자를 만들 수 없습니다.
  • built-in-types의 연산자를 overloading할 수 없습니다.

2.2. Binary Operator(+, -, *, / 등) Overloading

+를 예로 들겠습니다.

2.2.1. Member

//...
class vt{
    //...
    public:
        //...
        vt operator+(vt op2);
};

//...

vt vt::operator+(vt op2){
    double result_x=0, result_y=0;

    result_x = x + op2.x;
    result_y = y + op2.y;

    vt result(result_x, result_y);
    return result;
}

[Line 6]
vt operator+(vt op2);
op1+op2 연산을 한다고 치고 op2만 parameter로 넣습니다.

[Line 11]
vt vt::operator+(vt op2){
함수 정의 부분 시작입니다.

[Line 12]
double result_x=0, result_y=0;
결과의 x성분과 y성분을 선언했습니다.

[Line 14~15]
result_x = x + op2.x;
result_y = y + op2.y;
x, y는 vt class의 멤버 변수입니다. op2.x, op2.y는 parameter로 입력받은 값입니다.

[Line 17]
vt result(result_x, result_y);
vt 객체 result를 선언하되, result_x와 result_y를 생성자의 parameter로 넣습니다.

[Line 18]
return result;
result를 return합니다.

2.2.2. Non-member

//...
class vt{
    //...
    public:
        //...
        friend vt operator+(vt op1, vt op2);
};

int main(void){
    //...
}

vt operator+(vt op1, vt op2){
    int result_x=0, result_y=0;

    result_x = op1.x + op2.x;
    result_y = op1.y + op2.y;

    vt result(result_x, result_y);
    return result;
}

[Line 6]
friend vt operator+(vt op1, vt op2);
op1+op2 연산을 하기 위해 vt형 함수 operator+를 정의할 것입니다. operator+ 함수가 vt class의 private 변수에 접근할 수 있도록 friend를 선언합니다.

[Line 13]
vt operator+(vt op1, vt op2){
vt형 함수 operator+를 정의합니다.

[Line 14]
double result_x=0, result_y=0;
결과의 x성분과 y성분을 선언했습니다.

[Line 16~17]
result_x = op1.x + op2.x;
result_y = op1.y + op2.y;
모든 operand가 parameter로 불러와졌으므로 .을 이용해 각 성분에 접근합니다.

[Line 19]
vt result(result_x, result_y);
vt 객체 result를 선언하되, result_x와 result_y를 생성자의 parameter로 넣습니다.

[Line 20]
return result;
result를 return합니다.

2.3. 서로 다른 자료형 간의 교환법칙(ex. 벡터의 실수배)

b=ka와 같은 연산을 벡터의 실수배라고 합니다. 편의상 a를 v1, b를 v2라고 했을 때 v2=a*k=k*a가 되어야 합니다. 그러나 *를 2.2절에서의 방법으로 구현하고 나면 a*k는 되는데 k*a가 안 될 것입니다. 이는 double형+vt형 연산이 구현되지 않았기 때문입니다. 2.3절(이번 절)에서 double형+vt형 연산을 구현해 보겠습니다.

//...
class vt{
    //...
    public:
        //...
        vt operator*(vt op2);
};

vt operator*(double op1, vt op2);

int main(void){
    //...
}

vt vt::operator*(double op2){
    int result_x=0, result_y=0;

    result_x = x * op2;
    result_y = y * op2;

    vt result(result_x, result_y);
    return result;
}

vt operator*(double op1, vt op2){
    vt result=op2*op1;
    return result;
}

[Line 6, 15~23]
operator+와 동일한 방법으로 operator*를 멤버 함수 또는 non-member로 구현합니다. (예제는 멤버 함수입니다.)

[Line 9]
vt operator*(double op1, vt op2);
주의하실 것은 이 함수는 class의 멤버 함수가 아니라는 것입니다. class 밖에 전역 함수로 선언해 줍니다.

[Line 25~28]
vt operator*(double op1, vt op2){
vt result=op2*op1;
return result;
}
9행에서 선언한 함수를 정의합니다. 그냥 op1*op2op2*op1으로 바꿔주는 것으로 문제를 해결했습니다.

2.4. Unary Operator(단항연산자) Overloading

‘unary operator’라고 하면 !, ~ 같은 것들을 생각하시겠지만 제가 예제에 double형 변수를 사용하는 바람에 !, ~ 사용이 어렵습니다. 그런데 사실 +, -도 unary version이 있습니다. 음수를 생각해 보세요. -5의 -는 operand가 1개인 unary operator입니다. 그래서 사실 +, -도 unary version을 각각 따로 만들어야 합니다. 2.4절(이번 절)에서는 +, -의 unary version을 구현해 보겠습니다.
멤버 함수와 non-member 두 가지로 구현해 볼 것인데, unary operator overloading은 멤버 함수로 구현하는 것이 더 좋다고 합니다.

2.4.1. Member

//...
class vt{
    //...
    public:
        //...
        vt operator+();
};

//...

vt vt::operator+(){
    int result_x=0, result_y=0;

    result_x = x;
    result_y = y;

    vt result(result_x, result_y);
    return result;
}

+의 binary version에서 op1+op2 연산을 한다고 할 때 operator+ 함수의 parameter는 vt op2였습니다.
+의 unary version에서 +op1 연산을 한다고 할 때 operator+ 함수의 parameter는 없습니다.

2.4.2. Non-member

//...
class vt{
    //...
    public:
        //...
        friend vt operator+(vt op1);
};

int main(void){
    //...
}

vt operator+(vt op1){
    int result_x=0, result_y=0;

    result_x = op1.x;
    result_y = op1.y;

    vt result(result_x, result_y);
    return result;
}

+의 binary version에서 op1+op2 연산을 한다고 할 때 operator+ 함수의 parameter는 vt op1, vt op2였습니다.
+의 unary version에서 +op1 연산을 한다고 할 때 operator+ 함수의 parameter는 vt op1뿐입니다.

2.5. 대입 연산자(=) Overloading

대입연산자 =의 overloading은 멤버 함수로만 가능하고 non-member가 불가능하더라고요. 그리고 =는 특이하게 this를 사용해야 합니다.

//...
class vt{
    //...
    public:
        //...
        vt operator=(vt op2);
};

//...

vt vt::operator=(vt op2){
    this->x=op2.x;
    this->y=op2.y;
    return *this;
}

[Line 6]
vt operator=(vt op2);
op1=op2 연산을 한다고 치고 op2만 parameter로 넣습니다.

[Line 11]
vt vt::operator=(vt op2){
함수 정의 부분 시작입니다.

[Line 12~13]
this->x=op2.x;
this->y=op2.y;
this는 객체 자신의 포인터입니다. 따라서 this->x로 멤버 변수 x에 접근할 수 있습니다. 이 두 줄을 통해 멤버 변수 x, y의 값이 입력받은 op2.x, op2.y 값으로 바뀌는 것입니다.

[Line 14]
return *this;
this는 포인터이므로 객체 자기 자신을 return하기 위해 * 연산자를 this 앞에 붙여 줍니다.

2.6. 전위 증감 연산자(++v1, --v1) Overloading

벡터에 증감 연산자라니 무언가 이상하긴 하지만 (1, 1)을 더하거나 빼는 연산으로 정의해 보겠습니다. 대입 연산자 =와 마찬가지로 this를 사용해야 합니다. 2.6~2.7절에서는 ++만 다루어 보겠습니다.

2.6.1. Member

//...
class vt{
    //...
    public:
        //...
        vt operator++();
};

//...

vt vt::operator++(){
    ++this->x;
    ++this->y;

    return *this;
}

[Line 6]
vt operator++();
++op1 연산을 한다고 가정했을 때 parameter는 없습니다.

[Line 11~16]
함수 정의 부분입니다. this->x, this->y를 각각 1씩 증가시킵니다.

2.6.2. Non-member

//...
class vt{
    //...
    public:
        //...
        friend vt operator++(vt &op1);
};

int main(void){
    //...
}

vt operator++(vt &op1){
    ++op1.x;
    ++op1.y;

    return op1;
}

[Line 6]
friend vt operator++(vt &op1);
++op1 연산을 한다고 가정했을 때 parameter는 vt &op1입니다. op1 앞에 &를 꼭 붙이시기 바랍니다.

[Line 13~18]
함수 정의 부분입니다. op1.x, op1.y를 각각 1씩 증가시킵니다.

2.7. 후위 증감 연산자(v1++, v1--) Overloading

후위 증감 연산자는 언뜻 보면 ++, –로 똑같아 보이지만 각각 overloading 해야 합니다. 전위 증감 연산자와 구분하기 위해서 후위 증감 연산자 함수의 operand에 int를 추가합니다.

2.7.1. Member

//...
class vt{
    //...
    public:
        //...
        vt operator++(int);
};

//...

vt vt::operator++(int){
    vt temp=*this;

    ++this->x;
    ++this->y;

    return temp;
}

[Line 6]
vt operator++(int);
op1++ 연산을 한다고 가정했을 때 parameter는 int 하나뿐입니다.

[Line 11]
vt vt::operator++(int){
함수 정의 부분 시작입니다. parameter의 int형 변수는 후위 연산자라는 것을 표시하기 위한 것이므로 변수명을 붙이지 않아도 됩니다. 혹은 아무 이름이나 붙이셔도 됩니다.

[Line 12]
vt temp=*this;
vt 객체 temp를 만들어 *this를 복사합니다.

[Line 14~15]
++this->x;
++this->y;
this->x, this->y를 각각 1씩 증가시킵니다. 여기서는 그냥 전위 증감 연산자를 써도 됩니다. 어차피 return은 18행에서 따로 하기 때문입니다.

[Line 17]
return temp;
temp를 return합니다. 그러면 v1++ 연산을 한 자리에는 v1이 들어가고 그 다음부터는 v1+1이 들어가게 됩니다.

2.7.2. Non-member

//...
class vt{
    //...
    public:
        //...
        friend vt operator++(vt &op1, int);
};

int main(void){
    //...
}

vt operator++(vt &op1){
    vt temp=op1;

    ++op1.x;
    ++op1.y;

    return temp;
}

[Line 6]
friend vt operator++(vt &op1, int);
op1++ 연산을 한다고 가정했을 때 parameter는 vt &op1, int입니다. op1 앞에 &를 꼭 붙이시기 바랍니다.

[Line 13]
vt operator++(vt &op1){;
함수 정의 부분 시작입니다. parameter의 int형 변수는 후위 연산자라는 것을 표시하기 위한 것이므로 변수명을 붙이지 않거나 아무 이름이나 붙이셔도 됩니다.

[Line 14]
vt temp=op1;
vt 객체 temp를 만들어 op1를 복사합니다.

[Line 16~17]
++op1.x;
++op1.y;
op1.x, op1.y를 각각 1씩 증가시킵니다. 여기서는 그냥 전위 증감 연산자를 써도 됩니다. 어차피 return은 20행에서 따로 하기 때문입니다.

[Line 19]
return temp;
temp를 반환합니다.

2.8. 비교 연산자(<, <=, >=, >, ==, != 등) Overloading

양쪽이 같은지 비교하는 연산자 ==를 overloading해 보겠습니다. <, <=, >=, >, !=도 있지만 2.8절(이번 절)에서는 ==만 구현해 보겠습니다.

2.8.1. Member

//...
class vt{
    //...
    public:
        //...
        bool operator==(vt op2);
};

//...

bool vt::operator==(vt op2){
    if(x==op2.x && y==op2.y){
        return 1;
    }
    return 0;
}

[Line 6]
vt operator==(vt op2);
op1==op2 연산을 한다고 치고 op2만 parameter로 넣습니다.

[Line 11]
vt vt::operator==(vt op2){
함수 정의 부분 시작입니다.

[Line 12~14]
if(x==op2.x && y==op2.y){
return 1;
}
x, y는 vt class의 멤버 변수입니다. op2.x, op2.y는 parameter로 입력받은 값입니다. x성분끼리 같고 y성분끼리 같으면 같은 벡터라는 사실을 if문으로 구현하였습니다. if문의 조건을 만족하면 1을 return합니다.

[Line 15]
return 0;
if문의 조건을 만족하지 못하면(op1!=op2이면) 0을 반환합니다.

2.8.2. Non-member

//...
class vt{
    //...
    public:
        //...
        friend vt operator==(vt op1, vt op2);
};

int main(void){
    //...
}

bool operator==(vt op1, vt op2){
    if(op1.x==op2.x && op1.y==op2.y){
        return 1;
    }
    return 0;
}

[Line 6]
friend vt operator==(vt op1, vt op2);
op1==op2 연산을 하기 위해 vt형 함수 operator==를 정의할 것입니다. operator== 함수가 vt class의 private 변수에 접근할 수 있도록 friend를 선언합니다.

[Line 13]
vt operator==(vt op1, vt op2){
vt형 함수 operator==를 정의합니다.

[Line 14~16]
if(op1.x==op2.x && op1.y==op2.y){
return 1;
}
모든 operand가 parameter로 불러와졌으므로 .을 이용해 각 성분에 접근합니다. x성분끼리 같고 y성분끼리 같으면 같은 벡터라는 사실을 if문으로 구현하였습니다. if문의 조건을 만족하면 1을 return합니다.

[Line 17]
return 0;
if문의 조건을 만족하지 못하면(op1!=op2이면) 0을 반환합니다.

2.9. ostream(<<) Overloading

<<와 >>는 왼쪽 operand가 vt 객체가 아니기 때문에 non-member로만 구현할 수 있으며, 반드시 friend를 사용해야 합니다. 먼저 <<부터 구현해 보겠습니다.

예) cout << v1;

//...
class vt{
    //...
    public:
        //...
        friend ostream &operator<<(ostream&, const vt&);
};

int main(void){
    //...
}

ostream &operator<<(ostream &os, const vt &v){
    os << '(' << v.x << ", " << v.y << ')';
    return os;
}

[Line 6]
friend ostream &operator<<(ostream&, const vt&);
&operator<< 함수가 vt class의 멤버 변수에 접근할 수 있도록 friend를 선언합니다.

[Line 13]
ostream &operator<<(ostream &os, const vt &v){
parameter 이름은 ostream &os, const vt &v로 하겠습니다.

[Line 14]
os << ‘(‘ << v.x << “, “ << v.y << ‘)’;
cout 대신 os를 쓴다고 생각하고 작성하시면 됩니다.

[Line 15]
return os;
os를 반환합니다.

이제 v1이 (1, 3)일 때 cout << v1;을 실행하면 cout << ‘(‘ << 1 << “, “ << 3 << ‘)’;을 실행한 것과 같은 효과가 나타나서 (1, 3)이 출력됩니다.

2.10. istream(>>) Overloading

앞에서 말씀드린 대로 <<와 >>는 왼쪽 operand가 vt 객체가 아니기 때문에 non-member로만 구현할 수 있으며, 반드시 friend를 사용해야 합니다. 이번에는 >>를 구현해 보겠습니다.

예) cin >> v1;

//...
class vt{
    //...
    public:
        //...
        friend istream &operator>>(istream&, vt&);
};

int main(void){
    //...
}

istream &operator>>(istream &is, vt &v){
    string buf;

    is.ignore();    //skip '('
    getline(is, buf, ',');  //x성분 얻음
    v.x=stod(buf);
    getline(is, buf, ')');  //y성분 얻음
    v.y=stod(buf);

    return is;
}

[Line 6]
friend istream &operator>>(istream&, vt&);
&operator>> 함수가 vt class의 멤버 변수에 접근할 수 있도록 friend를 선언합니다. &operator<<와 달리 vt& 앞에 const를 붙이지 않습니다.

[Line 13]
ostream &operator>>(istream &is, vt &v){
parameter 이름은 istream &is, vt &v로 하겠습니다.

[Line 14]
string buf;
입력받은 문자열을 분할하기 위해서 우선 문자열을 받을 string을 선언합니다.

[Line 16]
is.ignore(); //skip ‘(‘
터미널에 (1, 3)을 입력한다고 가정하면 처음 ‘(‘를 건너뛰고 1을 인식해야 합니다. ‘(‘를 건너뛰기 위해 is.ignore();를 작성합니다.

[Line 17]
getline(is, buf, ‘,’); //x성분 얻음
x성분을 얻기 위해 getline 함수를 사용합니다. buf가 string이기 때문에 is.getline(char* _Str, std::streamsize _Count, char _Delim)이 아니라 getline(std::istream &_Istr, std::string &_Str, char _Delim)을 사용합니다. 구분자(_Delim)는 ‘,‘로 했습니다.

[Line 18]
v.x=stod(buf);
buf는 string이므로 double형 변수로 변환해 주어야 합니다. 이를 위해 stod 함수를 사용했습니다.

  • char* → 수
    • atoi: char* → int
    • atof: char* → double
  • string → 수
    • stoi: string → int
    • stof: string → float
    • stod: string → double

[Line 19]
getline(is, buf, ‘)’); //y성분 얻음
y성분을 얻기 위해 getline 함수를 사용합니다. 구분자(_Delim)는 ‘)‘로 했습니다.

[Line 20]
v.y=stod(buf);
buf는 string이므로 double형 변수로 변환해 주었습니다.

[Line 22]
return is; is를 반환합니다.

이제 cin >> v1; 코드가 있을 때 (1, 3)을 입력하면 v1에 (1, 3)이 저장됩니다.

2.11. 완성 예제 코드

소스코드가 길어서 첨부파일로 대체합니다. 멤버 함수 위주로 쓴 것과 Non-member 위주로 쓴 것으로 나누었습니다. 멤버 함수로 구현할 수 없는 것과 Non-member로 구현할 수 없는 것이 있어서 완벽하게 나누지는 못했다는 점 유의하시기 바랍니다.

3. Overriding

overriding은 class 상속과 관련된 개념입니다. parent class의 멤버 함수와 자기 class의 멤버 함수의 이름과 parameter가 각각 같은데 action이 다른 것이 overriding입니다. virtual function을 사용하여 구현합니다.

3.1. 배경 설명(virtual function을 왜 쓰는가?)

#include <iostream>
using namespace std;

class zoo{
    public:
        void cry(){cout << "동물원: 동물원은 울 수 없다. 동물이 울 뿐." << endl;};
};

class person: public zoo{
    public:
        void cry(){cout << "사람: 엉엉엉" << endl;}
};

class dog: public zoo{
    public:
        void cry(){cout << "개: 멍멍" << endl;}
};

class cat: public zoo{
    public:
        void cry(){cout << "고양이: 야옹" << endl;}
};

class cow: public zoo{
    public:
        void cry(){cout << "소: 음매" << endl;}
};

int main(void){
    cout << "[(1) 각 동물의 class 사용 시]" << endl;

    zoo zoo1;
    person person1;
    dog dog1;
    cat cat1;
    cow cow1;

    zoo1.cry();
    person1.cry();
    dog1.cry();
    cat1.cry();
    cow1.cry();
    cout << '\n';

    cout << "[(2) 각 동물의 class의 포인터 사용 시]" << endl;

    zoo* zoo2=new zoo;
    person* person2=new person;
    dog* dog2=new dog;
    cat* cat2=new cat;
    cow* cow2=new cow;

    zoo2->cry();
    person2->cry();
    dog2->cry();
    cat2->cry();
    cow2->cry();
    cout << '\n';

    cout << "[(3) zoo 포인터로 모든 동물 선언 시]" << endl;

    zoo* zoo3=new zoo;
    zoo* person3=new person;
    zoo* dog3=new dog;
    zoo* cat3=new cat;
    zoo* cow3=new cow;

    zoo3->cry();
    person3->cry();
    dog3->cry();
    cat3->cry();
    cow3->cry();
    cout << '\n';

    delete zoo2;    delete person2; delete dog2;    delete cat2;    delete cow2;
    delete zoo3;    delete person3; delete dog3;    delete cat3;    delete cow3;
    return 0;
}

이 코드를 작성하고 컴파일해 보겠습니다.

zoocry
62~66행에서 zoo*형으로 선언한 변수들이 실제로 어떤 객체인지와는 상관없이 68~72행의 cry()를 실행했을 때 zoo class의 멤버 함수 cry()가 호출되었습니다.
class 포인터 변수는 그 class를 상속하는 객체를 가리킬 수 있습니다. 다만, 이 경우 멤버 함수를 호출할 때 포인터의 자료형을 기준으로 호출합니다.
따라서 위 코드의 69~72행은 각각 person, dog, cat, cow이지만 포인터의 자료형이 zoo이기 때문에 zoo class의 멤버 함수 cry()가 호출된 것입니다.

3.2. Virtual Function

parent class의 멤버 함수를 virtual function으로 선언하면 상속받는 class의 멤버 함수가 호출되게 할 수 있습니다. 단, 상속받는 class에 해당 이름의 멤버 함수가 없으면 parent class의 멤버 함수가 호출됩니다.

#include <iostream>
using namespace std;

class zoo{
    public:
        virtual void cry(){cout << "동물원: 동물원은 울 수 없다. 동물이 울 뿐." << endl;};
};

class person: public zoo{
    public:
        void cry(){cout << "사람: 엉엉엉" << endl;}
};

class dog: public zoo{
    public:
        void cry(){cout << "개: 멍멍" << endl;}
};

class cat: public zoo{
    public:
        void cry(){cout << "고양이: 야옹" << endl;}
};

class cow: public zoo{
    public:
        void cry(){cout << "소: 음매" << endl;}
};

int main(void){
//...

(글이 너무 길어져서 중복되는 부분은 생략했습니다.)

eachcry
62~66행에서 zoo*형으로 선언한 변수들도 68~72행의 cry()를 실행했을 때 각각 자기 class의 멤버 함수 cry()가 호출되었습니다.

3.3. destructor(소멸자)는 virtual function으로 구현하는 것이 좋다.

상속받는 class의 destructor로 destruct해야 할 수 있기 때문에 destructor는 virtual function으로 구현하는 것이 좋습니다.
위의 예제들은 constructor, destructor가 딱히 필요없어서 안 썼는데, 3.5절에서 destructor를 virtual function으로 구현해 보겠습니다.

3.4. Pure Virtual Function

parent class에서는 함수를 아예 구현하지 않고 child class에서만 함수를 구현하는 방법도 있습니다. Pure Virtual Function이라는 것을 쓰면 되는데, parent class에서 함수 뒤에 =0;을 붙이면 됩니다. 이 경우 parent class로 객체를 생성할 수 없습니다.

//...
class zoo{
    public:
        virtual void cry()=0;
};
//...

error
위와 같이 zoo class에 virtual function을 선언하고 zoo 객체를 생성하려고 하면 컴파일 오류가 발생합니다.

3.5. 활용

포인터와 배열, 반복문을 함께 사용하면 overriding으로 코드를 간단하게 작성할 수 있습니다.
이번에는 destructor도 넣어 보았습니다.

#include <iostream>
using namespace std;

class zoo{
    public:
        zoo(){}
        virtual ~zoo(){cout << "동물 한 마리 자러 감" << endl;}
        virtual void cry()=0;
};

class person: public zoo{
    public:
        ~person(){cout << "사람 한 명 귀가" << endl;}
        void cry(){cout << "사람: 엉엉엉" << endl;}
};

class dog: public zoo{
    public:
        void cry(){cout << "개: 멍멍" << endl;}
};

class cat: public zoo{
    public:
        void cry(){cout << "고양이: 야옹" << endl;}
};

class cow: public zoo{
    public:
        void cry(){cout << "소: 음매" << endl;}
};

int main(void){
    zoo* zoo[4]={0};
    zoo[0]=new person;
    zoo[1]=new dog;
    zoo[2]=new cat;
    zoo[3]=new cow;

    for(int i=0; i<4; i++){
        zoo[i]->cry();
    }

    cout << '\n';

    for(int i=0; i<4; i++){
        delete zoo[i];
    }
    return 0;
}

eachcryonce

4. 최종 정리 및 글 마무리

Polymorphism 최종 정리
제 글을 읽어 주셔서 감사합니다. 다음에 만나요!

5. 참고 자료

1) ShoveLingLife. 2023. “[C++] stoi, stof, stol, stod 함수에 대해서 (string to int), 문자열 > 특정 타입”, A Game Programmer. (2023. 12. 22. 방문). https://devshovelinglife.tistory.com/455
2) BlockDMask. 2019. “[C언어/C++] atoi, atof, atol 함수 (char* to int)”, 개발자 지망생. (2023. 12. 22. 방문). https://blockdmask.tistory.com/331
3) 도도.__.. 2023. “C++ - 함수중첩(Function Overloading)”, 도슐랭스타. (2023. 12. 22. 방문). https://dodo5517.tistory.com/59
4) 작은울보. 2023. “C++ : Operator Overloading”, 방황하는 마음. (2023. 12. 22. 방문). https://jgs0314.tistory.com/4
5) Song 컴퓨터공학. 2023. “[C++] 연산자 중복 (Operator overloading) + 프렌드(friend) 개념”, Song 컴퓨터공학. (2023. 12. 22. 방문). https://songsite123.tistory.com/50
6) 초록색이젤다. 2023. “[C++] Operator Overloading”, 초록색이젤다. (2023. 12. 22. 방문). https://waterglass0105.tistory.com/55
7) 초록색이젤다. 2023. “[C++] Polymorphism”, 초록색이젤다. (2023. 12. 22. 방문). https://waterglass0105.tistory.com/46
8) heeaeeeee. 2023. “[C++ 프로그래밍] 13주차 overriding : 가상함수(virtual function)static”, With heeaeeeee. (2023. 12. 22. 방문). https://heeaeeeee.tistory.com/12