상속 (Inheritance)
상속이 필요한 이유
// Player 클래스
class Player{
private:
int x, y, speed;
public:
Player(int x, int y, int speed)
:x{x}, y{y}, speed{speed}
{
}
void Move(int dx, int dy){
x += dx * speed;
y += dy * speed;
void ShowPosition(){
cout << x << ", " << y << endl;
}
};
// Player 관리를 위한 클래스
// ->컨트롤 / 매니저 클래스라고 부름
class PlayerHandler{
private:
Player* playerList[50];
int playerNum;
public:
PlayerHandler() : playerNum{0}{}
void AddPlayer(Player* p){
playerList[playerNum++] = p;
}
void ShowAllPlayerPosition() const{
for(int i = 0; 0 < playerNum; i++){
playerList[i] -> ShowPosition();
}
}
~PlayerHandler(){
for(int i = 0 ; i < playerNum; i++){
delete playerList[i];
}
}
};
상속이 필요한 이유, 시나리오
Player 이외에 Enemy와 NPC가 추가된다면?
Enemy와 NPC의 이동 방식이 다르다면 어떻게 할까?
→ Enemy : dx * speed * 1.5f;
→ NPC : 이동 불가능
멤버 변수 추가 필요
//ex
Enemy* enemyList[];
NPC* npcList[];
클래스별 정보 추가기능 필요
클래스별 위치 정보 출력 반복문 필요
클래스별 해제 필요
→ 상속과 다형성을 활용해 적은 수정으로 기능 추가 가능하도록 설계 가능
상속 (Inheritance)의 정의
기존 클래스를 기반으로 새로운 클래스를 생성하는 방법
새로운 클래스는 기존 클래스의 데이터와 행동(함수)를 포함
기존 클래스를 재사용 가능하게 함
클래스들 간의 공통 속성에 집중하는 설계 방법
기존 클래스의 행동(함수)를 수정하여, 새로운 클래스의 행동을 새로 정의 가능
→ 기존 클래스의 행동을 수정할 필요는 없다.
관련 클래스의 예시
Player, Enemy, NPC, Boss, Hero, Super Hero, ect.
Account, Saving Account, Checking Account, Trust Account, etc.
Shape Line, Oval, Circle, Square, etc.
Player 예시 설계
Player
→ x, y, speed, hp, xp, Talk(), Move()
Enemy
→ x, y, speed, hp, gold, Talk(), Move()
NPC
→ x, y, dialog, Talk()
행동(함수)는 클래스마다 다를 수 있다.
공통 데이터 : x,y 공통 기능 : Talk()
class Entity{
//x, y, Talk()
};
class Player : public Entity{
//speed, hp, xp, Move()
};
class Enemy : public Entity{
//speed, hp, gold, Move()
};
class NPC : public Entity{
//dialog
};
Player 객체는 두 가지 타입을 모두 가진 상태가 됨
→ Player객체는 Player 타입이기도 하지만 Entity 타입이기도 하다
기본 클래스(base class/parent class/super class)
상속의 대상이 되는 클래스
유도 클래스(derived class/child class/sub class)
기본 클래스로부터 생성되는 클래스
데이터와 행동을 기본 클래스로부터 상속함
화살표는 기본 클래스쪽으로 향한다.
유도 클래스 (Deriving classes from existing class)
class Base{
//base class members...
};
class Derived : access-specifier Base{
//derived class members...
};
access-specifier : public, (private, protected)
//문법적으로는 존재하지만 잘 사용 안함
class QQQ{
};
class Derived : public QQQ{
};
int maain(){
QQQ b;
Derived b;
}
public 상속
가장 흔히 사용되는 상속방식
”is - a” 관계의 정의와 가장 일치하는 상속 방식
private와 protected 상속
”has -a”와 유사한 관계를 정의하는 상속
소유와 차이가 존재
타 언어에서는 protected/private 상속을 지원하지 않는 경우가 많음
Protected/private 상속은 public 상속만큼 활용도가 높지 않음
(다른 방식으로 구현)
class Entity{
protected:
int x, y;
public:
Entity(int x, int y)
: x{x}, y{y}
{
}
void ShowPosition(){
cout << x << ", " << y << endl;
}
void Talk(){
cout << "Hi!" << endl;
}
};
class Player: public Entity(
private:
int hp, xp, speed;
public:
Player(int x, int speed)
:Entity{x, y}, speed{speed}
{
}
void Move(int dx, int dy){
x += dx;
y += dy;
}
int main(){
Entity.entity{1, 1};
entity.Talk();
entity.ShowPosition();
Player.player1{2, 3, 1};
player1.Talk();
player1.ShowPosition();
}
protected 멤버 (Protected Member)
기본 클래스에서 접근 가능
유도 클래스에서 접근 가능
기본 또는 유도 클래스의 객체로부터는 접근 불가능
→ Private을 생각해보면 클래스에서는 접근 가능하고, 객체로부터는 접근이 불가능
→ Protected는 상속이 이루어지는 private라고 생각하면 펀함
→ Private은 유도 클래스에서 접근이 불가능 → private은 상속과 관계없이 무조건 (자신)클래스 내부에서만 접근이 가능하다
class Base
{
protected:
//protected members
};
protected 멤버 변수
class Base{
public:
int a;
protected:
int b;
private:
int c;
};
class Drived : public Base{
}
//유도 클래스에서 접근
//Public : a
//Protected : b
//c는 접근 불가
//접근할수 없는 것이지, 값이 메모리에 저장되지 않는 것은 아니다.
//sizeof(Base) == sizeof(Derived)
상속에서의 생성자와 소멸자
매우 중요한 상속에서의 생성자
유도 클래스는 기본 클래스의 멤버를 포함하므로, 유도 클래스가 초기화 되기 이전에 기본 클래스에서 상속된 부분이 반드시 초기화 되어야 함
유도 클래스 객체가 생성될 때
→ 먼저 기본 클래스의 생성자가 호출
→ 그 이후 유도 클래스의 생성자 호출
class Base{
public:
Base(){cout << "Base constructor" << endl;}
};
class Derived : public Base
{
public:
Derived(){cout << "Derived constructor"<< endl;}
};
//Base constructor
//Derived constructor
//유도클래스 객체 Derived.d를 생성하면 base 생성자 먼저 호출됨
상속에서의 소멸자
소멸자는 생성자와 반대 순서로 호출됨
즉 유도 클래스가 소멸될 때
→ 먼저 유도 클래스의 소멸자가 호출되고
→ 그 이후 기본 클래스의 소멸자가 호출된다.
class Base{
public:
Base(){cout << "Base constructor" << endl;}
~Base(){cout << "Base destructor" << endl;}
class Derived : public Base
{
public:
Derived(){cout << "Derived constructor"<< endl;}
~Derived(){cout << "Derived destructor"<< endl;}
};
int main(){
Derived d;
}
//Base constructor
//Derived constructor
//Derived destructor
//Base destructor
생성자와 소멸자의 상속
유도 클래스는 기본 클래스의 생성자, 소멸자 및 오버로딩된 대입 연산자를 상속하지 않음
대신, 기본 클래스의 생성자, 소멸자 및 오버로딩된 대입 연산자를 유도 클래스로부터 호출이 가능함
class Base{
private:
int value;
public:
Base(): value{0}{cout << "Base no-args constructor" <<endl;}
Base(int x): value{x}{cout << "Base (int) overloaded constructor"
<< endl;}
~Base(){cout << "Base destructor" << endl;}
};
class Derived : public Base{
private:
int double_value;
public:
Derived():double_value{0}{cout << "Derived no_args constructor"
<< endl;}
Derived(int x):double_value{x*2}{cout << "Derived(int) overloaded constructor"
<< endl;}
~Derived(){cout << "Derived destructor" << endl;}
};
int main(){
Base b; // value : 0
Base b{100}; //value : 100
Derived d; //value 0, double_value : 0
Derived d{1000}; // value: 0 double_value : 2000
return 0;
}
기본 클래스의 생성자로 인자 전달
(Passing Arguments to Base Class Constructor)
기본 클래스의 어떤 생성자를 호출할지 결정해 줄 수 있어야 한다.
유도 클래스의 생성자에, 초기화 리스트를 활용해 사용자가 원하는 기본 클래스의 생성자 호출 가능
class Base{
public:
Base();
Base(int);
...
};
Derived::Derived(int x)
:Base{x}, {...}
{
//dervied constructor code
}
class Base{
private:
int value;
public:
Base(): value{0} {cout << "Base no-args constructor" << endl;}
Base(int x): value{x} {cout << "Base (int) overloaded" << endl;}
~Base(){cout << "Base destructor" << endl;}
};
class Derived: public Base{
private:
int double_value;
public:
Derived():Base{}, double_value{0}
{cout << "Derived no_args constructor" << endl;}
Derived(int x): Base{x}, double_value{x*2}
{cout << "Derived (int) overloaded constructor" << endl;}
int main(){
Base b; // value: 0
Base b{100}; // value : 100
Derived d; // value: 0, double_value : 0
Derived d{1000} // value : 1000, double_value : 2000
return 0;
}
복사 생성자 상속(Copy Constructor)
기본 클래스로부터 상속되지 않음
(상속 전과 마찬가지로) 컴파일러가 자동생성하지만, 필요한 경우 직접 구현 해야함
기본 클래스에서 구현한 복사 생성자 호출 가능
유도 클래스의 복사 생성자
기본 클래스의 복사 생성자를 직접 호출 가능
Slice 과정을 거침
이동 생성자도 동일하게 동작
Derived::Derived(const Derived &other)
:Base(other), doubleValue{other.doubleValue}
{
//code
}
유도 클래스의 복사 생성자 예시
class Base{
int value;
public:
...
Base(const Base &other) : value{other.value}{
cout << "Base Copy Constructor" << endl;
}
};
class Derived:public Base{
int doubled_value;
public:
...
Derived(const Derived &other)
: Base(other), doubled_value(other.doubled.value){
cout << "Derived Copy Constructor" << endl;
}
};
복사 생성자의 구현 가이드(중요)
유도 클래스에서 사용자가 복사 생성자를 구현하지 않은 경우,
→ 컴파일러가 자동으로 생성하며, 기본 클래스에 있는 인자를 받지 않는 생성자를 호출
유도 클래스에서 사용자가 복사 생성자를 구현한 경우,
→ 명시하지 않으면 기본 클래스의 인자를 받지 않는 생성자를 호출
→ 기본 클래스를 위한 복사/이동 생성자를 명시해 줄 수 있다.
따라서, 포인터형 멤버 변수를 가지고 있는 경우, 기본 클래스의 복사/이동 생성자를 호출하는 방법에 대해 반드시 숙지해 두어야 함
→ 유도 클래스 멤버 변수에 대한 깊은 복사 고려
상속과 멤버함수(Using Member Function of Base Class)
기본 클래스의 멤버 함수 사용
유도 클래스는 기본 클래스의 멤버 함수를 직접 호출 가능
→ Public / protected인 멤버 함수의 경우에 한함
유도 클래스는 기본 클래스의 멤버 함수를 오버라이드 또는 재정의 가능
다형성의 구현을위해 중요한 기능
정적 바인딩(타입을 기준으로)
유도 클래스와 기본 클래스에 같은 이름과 인자를 갖는 함수가 2개 존재한다면?
→ 타입을 기준으로 호출한다.
Player p{5, 5, 10};
p.Talk();
//p는 Entity를 상속받은 Player이기 떄문에 Player에 있는 Talk 실행
Entity e{1, 1};
e.Talk();
//e는 entity이기 때문에 entity에 있는 Talk 실행
정적 바인딩과 그 한계
정적 바인딩은 컴파일시 어떤 함수가 호출될지를 결정하는 방식
C++의 기본은 정적 바인딩
→ 가장 아래의 경우, 동적 바인딩을 사용하여 기능의 확장 가능
Entity e{1,1};
e.Talk() //Entity::Talk Call
Player p{1,1,2};
p.Talk(); //Player::Talk Call
Entity *ePtr = new Player{1, 1, 2};
ePtr -> Talk(); // ??? player가 아닌 entity인줄 컴파일러는 앎
”IS - A”관계
기본 클래스 객체를 사용하는 곳에는 항상 유도 클래스 객체를 사용 가능
→ 유도 클래스에 기본 클래스의 멤버가 모두 포함되기 때문(superset)
→ Player 객체는 두 가지 타입을 모두 가진 상태가 됨!
void TalkSomething(Entity e){
e.Talk();
}
void ShowSomething(Entiy e){
e.ShowPosition();
}
int main(){
Player p{0, 0, 1};
TalkSomthing(p);
ShowSomething(p);
}
//*Entity 대신, p를 인자로 넣어도 오류가 없다.
int main(){
Entity p = Player {1,1,1};
Entity* ePtr = new Player{1, 1, 1};
Entity& pRef = p;
pRef.Showpotision();
Player* pPtr = new Entity{1, 1, 1};
//반대의 경우 error
'Language > C++' 카테고리의 다른 글
12.연산자 오버로딩 (1) | 2023.12.25 |
---|---|
11.다형성 (1) | 2023.12.25 |
09.this-const-static-friend (1) | 2023.12.22 |
08.생성자와 소멸자 (0) | 2023.12.22 |
07.OOP (0) | 2023.12.22 |
댓글