Language/C++

[TheSSEN/C++] 8. 다형성

coco_daddy 2025. 1. 27. 17:34
※ LIG Nex1 The SSEN Embedded SW School에서 진행된 내용을 정리한 포스팅입니다.

다형성

다형성

  • 동일한 코드가 실행 방법에 따라 다르게 동작하기 위함이다.
  • 함수 오버라이딩
    • 상속받은 멤버 함수를 하위 클래스에 적합하게 재정의
    • 함수의 프로토타입을 그대로 가져간다.
  • 업/다운 캐스팅
    • 상속 관계에서 사용하는 캐스팅
  • 가상함수
    • 재정의를 하더라도 실제 할당된 객체의 함수를 찾아가도록 하는 키워드

오버라이딩

  • 상속 받는 객체의 함수가 적합하지 않은 경우 재정의를 통해 기능을 확장하거나 수정한다.
    • 정적 호출(정적 바인딩)은 타입을 따라간다.
class Point {
protected:
    int x;
    int y;

public:
    Point():x(0), y(0) {}
    Point(int x, int y): x(x), y(y) {}
    void printPoint() {
        cout << "x: " << " y: " << y << endl;
    }
};

class Point3D : public Point {
private:
	int z;
public:
    Point3D():z(0) {}
    Point3D(int x, int y, int z): Point(x, y), z(z) {}

	// overriding
	printPoint() {
	    Point::printPoint(); // 부모의 멤버 함수를 명시적으로 호출
		cout << "z: "<< z << '\n';
	}
};

업/다운 캐스팅

  • 상속 관계에서 하위 객체 주소를 상위 타입 포인터에 할당하는 것
  • 가상함수가 아니라면 재정의 함수 호출은 포인터의 타입을 따라간다.
  • 상위 클래스의 타입에 정의된 멤버만 사용 가능하다.
// up casting
Point *pp = &p;
pp->printPoint();

 

메서드의 오버로딩을 다형성을 활용하여 개선하기

Computer와 Tv, Audio 객체가 있고, 이를 구매하는 buyer 클래스가 각각의 클래스에 해당하는 buy 함수를 오버라이딩 한 상태의 소스코드를 개선해보자.

class: Computer, Tv, Audio
class: buyer
    -> var: money, point
    -> func:
        buy(Tv tv);
        buy(Computer computer);
        buy(Audio audio);
class Appliance {};
class Computer: public Appliance {};
class Tv: public Appliance {};
class Audio: public Appliance {};
class Buyer {
    int budget;
    int point;
public:
    Buyer();
    void buy(Appliance &tar);
};

Appliance 라는 전체를 포괄하는 클래스로 하위 객체를 묶어 보일러 플레이트 코드를 개선할 수 있다.

int main(void) {
    Buyer buyer;
    Appliance *himart[3];

    himart[0] = new Tv();
    himart[1] = new Computer();
    himart[2] = new Audio();
    for (int i = 0; i < 10; ++i)
        buyer.buy(*himart[rand()*10 % 3]);
    for (int i = 0; i < 3; ++i)
        delete himart[i];
    return 0;
}

다운 캐스팅

  • 업 캐스팅된 포인터를 원래 타입으로 캐스팅 하는 것.
  • 캐스팅 연산자가 필요하다.
  • 하위 클래스의 멤버에 접근해야 하는 경우 사용한다.
*((Point3D*) p).getZ();
  • typeinfo 헤더의 typeid 함수를 활용하여 실제 할당된 객체의 클래스의 정보를 가져올 수 있다.
 

std::type_info - cppreference.com

The class type_info holds implementation-specific information about a type, including the name of the type and means to compare two types for equality or collating order. This is the class returned by the typeid operator. The type_info class is neither Cop

en.cppreference.com

Parent *p = new Parent(3);
Child *c = new Child(1, 2);
Parent *pc = c;
  • typeid의 name을 출력하면 다음과 같은 값을 얻을 수 있다.
typeid(p): P6Parent
typeid(c): P5Child
typeid(pc): P6Parent
typeid(*p): 6Parent
typeid(*c): 5Child
typeid(*pc): 5Child
  • 비교를 통해 실제 할당된 객체를 찾을 수 있다.
if (typeid(*pc) == typeid(Child))
	cout << "pc is Child\n";
else if (typeid(*pc) == typeid(Parent))
	cout << "pc is Parent\n";

가상 함수

  • 함수 정의 시 앞에 virtual 키워드를 붙인다.
  • 함수 호출 시 동적 바인딩을 통해 호출된다.
    • 정적 바인딩은 타입을 따라간다.(컴파일 시 결정된다.)
    • 동적 바인딩은 실제 생성된 객체를 찾아간다.
  • 하나 이상의 가상함수를 만들면 가상함수 테이블(V-table)이 생성된다.
    • 함수 호출 시 V-table을 참조하여 호출할 함수를 결정한다.(런타임 시 결정된다.)

순수 가상 함수

  • 피카츄와 꼬부기가 상속 받고 있는 포켓몬 클래스를 생각해보자.
    • 포켓몬 클래스는 객체를 생성하기 위해 존재하는 클래스가 아니다.
    • 상속의 목적으로만 존재한다.
  • 구현의 내용이 없음을 = 0 으로 명시한다.
virtual void eatSomthing() = 0;

추상 함수

  • 구현문 없이 함수의 선언만 갖고 있는 함수
  • 상속받은 하위 클래스에서 구현해야 한다.
  • 인터페이스를 제공하는 것이 목적이다.

추상 클래스

  • 추상 함수를 하나라도 갖고 있는 클래스
  • 객체 생성이 불가능하다.
class PocketMon
{
public:
	PocketMon();
	PocketMon(string name, string img);
	virtual void eatSomthing() = 0;
	virtual void sleep() = 0;
	virtual bool playAlone() = 0;
	virtual bool doExercise() = 0;
	virtual void lvCheck() = 0;
	void printState();
};

※ 질문, 개선점, 오류가 있다면 댓글로 남겨주세요 :)