bdfgdfg

[C++ 객체지향] 상속 및 추상화 본문

게임프로그래밍/C++

[C++ 객체지향] 상속 및 추상화

marmelo12 2022. 1. 7. 22:03
반응형

객체지향 프로그래밍 언어에서는 상속이라는 키워드가 존재하는데

이름 그대로 부모 클래스의 멤버 함수와 멤버 변수를 그대로 물려받는것을 의미 한다.

 -> 이 떄 물려받은 클래스를 자식 클래스 혹은 파생 클래스라 한다.

장점은 공통되는 특징(멤버들)들을 다시 작성할 필요없이 필요한 기능이 담긴 클래스를 상속받으면 된다.

 -> 코드의 재사용성 UP

 

 

사용하는 방법은 간단하다

class 클래스 이름 : 접근지정자 부모 클래스명
{
	//내용.
};

즉 Human이라는 클래스가 존재하고, Student라는 클래스가 존재한다고 보자.

상속을 정하는 기준에서 유명한 방법인 IS-A, Has-A 방법이 있다. A와 B클래스가 있고 상속관계를 정하려고 해본다.

IS-A : B는 ~A이다. (상속 관계)

Has-A : B는 A를 가진다. (포함 관계)

 -> 포함 관계 ex) 인벤토리 클래스를 기사(Knight)클래스가 가진다. -> Has-A

 

여기서 A를 사람. B를 학생이라고 하면 HAS-A방법이 아닌 IS-A방법이 알맞다.

class Human
{
protected:
	Human(int age, int height) : m_age(age),m_height(height) {}
	virtual ~Human() {}
public:
	void    PrintAge() { std::cout << m_age << std::endl; }
public:
	int		m_age;
	float	m_height;
	//..등등
};
class Student : public Human
{
public:
	Student(int mathGrade, int age, int height) : Human(age, height), m_mathGrade(mathGrade)
	{

	}
public:
	int m_mathGrade;
};

 

 

이렇게 부모클래스 Human을 상속받은 Student는 생성자가 호출되기 전 부모 생성자를 호출하여 부모의 초기화부터

진행한다.

그리고 부모의 멤버에 접근할 수 있는지 확인해보면

부모의 멤버를 모두 물려받았기에 접근이 가능하다.

 -> 물론 접근 지정자가 public으로 열려있기에 가능.

 

그런데 부모 클래스를 자식 클래스가 상속받을 때 사용되는 접근 지정자는 무슨 의미일까.

class Student : public Human

출처 -&amp;amp;amp;nbsp;https://skagh.tistory.com/2

그것은 위의 그림과 같이 

public 으로 상속받은 자식 클래스는 부모의 public은 그대로 public, protected도 그대로 proteced.private도 그대로 private이다.

즉 부모 클래스의 멤버에서 사용한 접근 지정자가 온전히 그대로 물려받는것.

 

다만 protected와 private 상속은 다르다.

protected상속은 private 지정자인 멤버를 제외한 모두 protected로 변하게 된다. 그렇기에 외부에서는 호출할 수 없고. 자식 클래스내에서만 호출이 가능.

priviate상속은 모두 다 private 상속이다. 즉 부모 클래스에서 public,protected의 지정자를 가진 멤버들이라 할지라도

자식 혹은 외부에서는 접근이 불가능하다.

 

다중 상속

C++는 다중 상속을 지원한다. (2개 이상의 부모 클래스를 상속할 수 있음)

다중 상속은 편리해보이지만 당연히 여러가지 문제점이 있다.

 

1. 여러 부모 클래스중 멤버 이름이 겹치는 경우.

class A
{
protected:
	int GetID() { return m_id; }
private:
	int m_id = 10;

};
class B
{
protected:
	int GetID() { return m_id; }
protected:
	int m_id = 20;
};

class C : public A, public B
{

};

위의 코드는 오류가 나지않는다. 같은 이름의 멤버 함수,변수라고 할지라도 어떤 클래스의 함수인지는 명확하기 떄문이다.

다만 사용자가 함수를 호출할때는 조심해야 한다.

 -> 에러를 바로 잡아주긴 한다.

이렇게 GetID라는 함수가 모호하다고 뜬다. 즉 부모 클래스의 A,B중 어느 함수를 호출해야할지 컴파일러 입장에선 

알 수가 없는 것.

그렇기에 어떤 부모의 함수인지를 명확히 명시해야 한다.

 

2. 다이아몬드 상속의 문제

 

사실 이게 제일 문제.

출처 -&amp;nbsp;https://velog.io/@joygoround/36.-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%81%EC%86%8D

위와 같이 A클래스를 상속받은 B와 C클래스가 있다고 보자.

그리고 D클래스는 B와 C를 상속받는데 여기서 발생하는 문제점.

이 경우는 A라는 클래스를 부모로 가진 B와 C클래스가 D에게 상속된다는게 문제다.

class A
{
protected:
	int GetID() { return m_id; }
public:
	int m_id = 10;

};
class B : public A
{
};
class C : public A
{
};


class D : public B,public C
{
public:
};

위의 그림과 같은 클래스의 상속구조 코드를 보자.

이 상태에서 D클래스의 객체를 생성하여 m_id에 접근해보면

컴파일러 입장에서는 누구의 m_id를 가리키는건지 알수가없어 에러를 준다.

위의 문제는 부모 클래스에 접근 지정자를 사용하면 해결은 가능하다.

다만 이걸 해결했다고는 할 수 없는게 문제는 의미가 같은데 메모리가 더 붙어서 올라간다는 것.

실제로 D클래스의 크기와 따로 K클래스를 정의해 B만 상속받게 하여 서로 크기를 비교해보면

class D : public B,public C
{
public:
};


class K : public B
{

};
int main()
{
	D d;
	K k;
	std::cout << sizeof(d) << std::endl;
	std::cout << sizeof(k) << std::endl;
	
	return 0;
}

D객체는 8바이트이고 k객체는 4바이트인것을 알 수 있다.

 

 

인터페이스(Interface)

 

다중 상속은 단점만 존재하는것 같다.

하지만 장점도 존재하는데 C++에서는 C#과 같은 인터페이스 문법이 존재하지 않는다.

여기서 C#에서는 안되는 다중상속을 C++에서 인터페이스와 비슷하게 사용할 수 있다. 바로 다중상속과 순수 가상함수를 이용하는 방법이다.

class Human
{
protected:
	int m_hp = 100;
	int m_mana = 50;
};

class IFlyable
{
public:
	virtual void Fly() abstract;
};

class Knight : public Human, public IFlyable
{
public:
	void Fly() override
	{
		std::cout << "기사 날다~" << std::endl;
	}
};

위와 같이 단순히 인터페이스의 문법과 거의 유사하게 사용할 수 있다.

여기서 인터페이스와 같은 역할을 하는 클래스는 이름앞에 대문자로 I를 붙이는것도 좋은 방법이다.

 -> 인터페이스라는 것을 이름에서 명시.

 

혹은 define 전처리기를 이용하여 인터페이스를 더욱 확실히 명시하는것도 좋은 방법.

#define Interface class

class Human
{
protected:
	int m_hp = 100;
	int m_mana = 50;
};

Interface IFlyable
{
public:
	virtual void Fly() abstract;
};

class Knight : public Human, public IFlyable
{
public:
	void Fly() override
	{
		std::cout << "기사 날다~" << std::endl;
	}
};

 

추상화

간단히 말해 공통의 속성이나 기능을 묶어 이름을 붙이는 것이다.

 - 객체지향에서 추상화라는 개념은 객체의 공통된 속성과 행위를 추출하는 것을 의미한다.

 

예로들어 SM5, 아우디, 봉고차, K5등등..(차 종류를 잘모른다..)

위의 각각의 차 객체들을 하나로 묶을려고 할 때 우리는 '자동차'라는 개념으로 추상화할 수 있다.

즉 코드로 표현해보면 Car라는 클래스가 존재하고 모든 차들이 가지고있는 차 엔진,페달등등.. 공통된 속성과 기능들을

묶어서 하나의 추상화 클래스를 만들 수 있다.

 

class Car
{
protected:
	virtual void StartEngine() abstract;
};

class HyundaiCar : public Car
{
public:
	void StartEngine() override
	{
		std::cout << "시동키기" << std::endl;
	}
};

class Granger : public HyundaiCar
{
public:
	void StartEngine() override
	{
		std::cout << "그랜져 시동 키기" << std::endl;
	}
};

차의 공통된 기능들이 잘 생각나지 않아 시동을 켜는 함수만을 넣었는데.

위와 같이 Car가 가지는 공통된 기능이 있을 것이고 현대차는 그 기능이 존재하지만 차 브랜드마다 그 기능이 조금씩 다를 것이며(맞나?) 현대차의 그랜져도 세부적으로는 그 기능이 다를것이다.

중요한것은 '자동차'라는 기능을 한데 묶어 그것을 상속함으로서 공통된 기능을 가져갈 수 있으며, 각 차들이 가지는 기능을 재정의하여 다형성까지도 가져갈 수 있는 것.

 

반응형
Comments