본문 바로가기

Language/C++

[C++] 다형성(Polymorphism)-(1) - 컴도리돌이

728x90

outline

Pointer, References and Inheritance

Virtual Function

Virtual Destructor

Caution : Object Slicing


C++에서 다형성(Polymorphism in C++)

  • 같은 함수 호출에 대해 서로 다른 방법으로 응답을 하는 것을 객체지향에서 다형성이라 한다.

포인트, 주소 그리고 상속(Pointers, References and Inheritance)

  • C++에서 다형성을 사용하기 전에, 상속에서 포인터와 주소 연산이 어떻게 사용되는지 이해가 필요하다.
#include <iostream>
using namespace std;

class Person {
 public:
   void Talk() {
   cout << "talk" << endl;
   }
};

class Student : public Person {
 public:
   void Study() {
   cout << "study" << endl;
   }
};

class CSStudent : public Student {
 public:
   void WriteCode() {
   cout << "write_code" << endl;
   }
};

C -> B -> A

상속에서 포인터(Pointers with Inheritance)

  • 기본 클래스(base class) B의 포인터는 B 클래스의 주소를 저장할 수 있다.

  • 기본 클래스(base class) B의 포인터는 파생 클래스(derived class) C의 주소를 저장할 수 있다.

  • 기본 클래스(base class) B의 포인터는 기본 클래스 A의 상위 개체의 주소를 저장할 수 없다.

  • C는 B이다, 그러나 A는 B가 아니다.

int main() {
   Person* p1 = new Person;
   Person* p2 = new Student;
   Person* p3 = new CSStudent;
   Student* s1 = new Person; // error
   Student* s2 = new Student;
   Student* s3 = new CSStudent;
   delete p1;
   delete p2;
   delete p3;
   delete s1;
   delete s2;
   delete s3;
   return 0;
}
int main() {
   Student st;
   
   Person* person_st = &st; // ok
   Student* student_st = &st; // ok
   CSStudent* csstudent_st = &st; //error!
   
   CSStudent csst;
   
   Person* person_csst = &csst; // ok
   Student* student_csst = &csst; // ok
   CSStudent* csstudent_csst = &csst; //ok
   return 0;
}

  • 파생 클래스 B의 포인터는 부모 클래스 A의 member에 접근할 수 있다.

  • 파생 클래스 B의 포인터는 파생 클래스 B의 member에 접근할 수 있다.

  • 파생 클래스 B의 포인터는 자식 클래스 C의 member에 절대 접근할 수 없다.

int main() {
   Student st;
   Person* person_st = &st;
   Student& student_st = st;

   person_st->Talk();
   person_st->Study(); // error!
   person_st->WriteCode(); // error!
   
   student_st->Talk();
   student_st->Study();
   student_st->WriteCode(); // error!
   
   return 0;
}

상속에서 주소연산(References with Inheritance)

  • 기본 클래스 B의 reference는 기본 클래스 B의 객체를 언급할 수 있다.

  • 기본 클래스 B의 reference는 자식 클래스 C의 객체를 언급할 수 있다.

  • 기본 클래스 B의 reference는 부모 클래스 A의 객체를 절대 언급할 수 없다.

int main() {
   Student st;

   Person& person_st = st; // ok
   Student& student_st = st; // ok
   CSStudent& csstudent_st = st; //error!

   CSStudent csst;

   Person& person_csst = csst; // ok
   Student& student_csst = csst; // ok
   CSStudent& csstudent_csst = csst; //ok
   return 0;
}

  • 파생 클래스 B의 reference는 부모 클래스 A의 멤버에 접근할 수 있다.

  • 파생 클래스 B의 reference는 파생 클래스 B의 멤버에 접근할 수 있다.

  • 파생 클래스 B의 reference는 자식 클래스 C의 멤버에 절대 접근할 수 없다.

int main() {
   Student st;
   Person& person_st = st;
   Student& student_st = st;
 
   person_st.Talk();
   person_st.Study(); // error!
   person_st.WriteCode(); // error!
   
   student_st.Talk();
   student_st.Study();
   student_st.WriteCode(); // error!

   return 0;
}

추상 함수(Virtual Functions)

  • 함수의 선언한 부분만 있고 구현 부분이 없는 함수를 말한다. 선언된 추상 클래스는 하위 클래스에서 구현되는데, 각각의 하위 클래스에서 상속받은 추상 함수를 서로 다른 방법으로 구현할 수 있다.
// Vehicle classes.
class Vehicle {
 public:
   virtual void Accelerate() {
   cout << "Vehicle.Accelerate";
   }
};

class Car : public Vehicle {
public:
   virtual void Accelerate() {
   cout << "Car.Accelerate";
   }
};

class Truck : public Vehicle {
public:
   virtual void Accelerate();
   cout << "Truck.Accelerate";
   }
};

// Main routine.
int main() {
   Car car;
   Truck truck;
   Vehicle* pv = &car;
   pv->Accelerate();
   // Outputs Car.Accelerate.
   
   pv = &truck;
   pv->Accelerate();
   // Outputs Truck.Accelerate.
   
   Vehicle vehicle;
   pv = &vehicle;
   pv->Accelerate();
   // Outputs Vehicle.Accelerate.
   
   return 0;
}

추상 소멸자(Virtual Destructor)

class A {
public:
   A() { cout << " A"; }
   virtual ~A() { cout << " ~A"; }
};

class AA : public A {
public:
   AA() { cout << " AA"; }
   virtual ~AA() { cout << " ~AA"; }
};

int main() {
   A* pa = new AA; // OK: prints ' A AA'.
   delete pa; // OK: prints ' ~AA ~A'.
   return 0;
}
  • 추상 소멸자를 사용하지 않는다면, pa를 삭제(delete)할 때 단순히 A 클래스에서만 소멸(destructor)이 발생된다. 이런 문제가 발생이 된다면 메모리 부족(memory leak) 현상을 일으킬 수 있다. 추상 소멸자를 사용함으로써 컴파일러에게 추상 함수가 존재하는 것을 알려줄 수 있다.

object slicing

  • 상속받은 클래스의 인스턴스를 부모 클래스의 인스턴스로 복사함으로써 상속받은 클래스가 가지고 있던 정보가 손실되는 것을 말한다.

class Base { int x, y; };

class Derived : public Base { int z, w; };

int main() {
   Derived d;
   Base b = d; // Object Slicing, z and w of d are sliced off
}
  • 위에 예시를 보면 클래스 d안에 멤버는 x, y, w, z가 존재한다. 이 상태에서 x, y 멤버만 존재하는 클래스 b에 단순히 d를 복사를 한다면 저장할 공간이 없어서 w, z의 정보가 손실이 된다.