13) 클래스 (4) - 상속
13-1. 파생 클래스
파이썬에서는 class 자식클래스(부모클래스1, 부모클래스2, ...)
의 형식으로 클래스 상속을 할 수 있었습니다.
C++에서도 비슷하게 파생클래스(derived class)라는 개념이 존재하여 부모클래스(base class)의 멤버들을 상속받을 수 있습니다.
예시) 문법
class 파생클래스이름 : 접근제한자1 클래스1, 접근제한자2 클래스2...
{
// 파생클래스 멤버 리스트
}
상속을 받을 때, 옵션으로 부모 클래스에 대한 접근제한자를 설정해야 합니다.
13-2. 클래스 접근제한자
이전에는 클래스 안의 멤버에 접근제한자를 설정하여 접근 권한을 조정했습니다.
상속을 위해서는 부모 클래스에도 접근제한자를 설정해야하는데요,
역시 접근제한자는 public, private, protected 3가지가 있으며, 이에 따른 차이는 다음과 같습니다.
- Type of Inheritance
- Public Inheritance
- 부모 클래스의 public 멤버: 파생 클래스의 public 멤버와 동일하게 취급
- 부모 클래스의 protected 멤버 : 파생 클래스의 protected 멤버와 동일하게 취급
- Protected Inheritance
- 부모 클래스의 public, protected 멤버 : 파생클래스의 protected 멤버와 동일하게 취급
- Private
- 부모 클래스이 public, protected 멤버 : 파생클래스의 private 멤버와 동일하게 취급
- Public Inheritance
- 공통점 : 부모 클래스의 private 멤버는 직접 접근이 불가능함. 오직 public & protected 멤버를 이용하여 접근가능함.
13-3 protected 접근제한자
protected 접근제한자는 파생 클래스와 관련이 있습니다.
Access | public | protected | private |
---|---|---|---|
Same class | yes | yes | yes |
Derived classes | yes | yes | no |
Outside classes | yes | no | no |
부모 클래스에서 protected로 설정된 멤버는 파생 클래스에서는 접근이 가능합니다.
ex) Shape 클래스의 파생 클래스에서 부모 멤버에 접근하기
class Shape{
protected:
std::string name_;
};
class Rectangular: public Shape{
public:
void SetName(std::string name);
void PrintName();
};
void Rectangular::SetName(std::string name){
name_ = name; // 만약 name_이 private면 이게 안됨!
}
void Rectangular::PrintName(){
std::cout << "모양의 이름: " << name_;
}
int main(){
Rectangular rec;
// rec.name_ 과 같은 접근은 허용되지 않음!
rec.SetName("직사각형");
rec.PrintName(); // 모양의 이름: 직사각형
}
그 외에도 protected 멤버는 프렌드 클래스에서도 접근이 가능하다는 특징이 있습니다.
13-4. 생성자
이제 파생 클래스의 생성자에 대해서 알아보겠습니다.
ex) 파생클래스의 생성자를 정의하는 방식
Rectangular::Rectangular(std::string name, int height, int width): Shape(name){ // 파생클래스(매개변수): 부모클래스(인수)
height_ = height, width_ = width;
}
부모클래스의 생성자를 상속하는 느낌으로 파생클래스의 생성자를 정의할 수 있습니다.
이 때, 부모클래스의 생성자가 먼저 호출되기 때문에 name 변수는 이미 초기화되었으므로 굳이 함수에 넣지 않아도 될 것입니다.
ex) 파생클래스의 생성자를 호출하기
class Shape{
protected:
std::string name_;
public:
Shape(std::string name);
};
class Rectangular: public Shape{
private:
int width_, height_;
public:
Rectangular(std::string name, int width, int height);
void PrintName();
void PrintLength();
};
Shape::Shape(std::string name){
name_ = name;
}
Rectangular::Rectangular(std::string name, int width, int height): Shape(name){
width_ = width, height_ = height;
}
void Rectangular::PrintName(){
std::cout << "모양의 이름: " << name_ << std::endl;
}
void Rectangular::PrintLength(){
std::cout << "가로 : " << width_ << " 높이 : " << height_;
}
int main(){
Rectangular rec = {"직사각형", 10, 15};
rec.PrintName(); // 모양의 이름: 직사각형
rec.PrintLength(); // 가로 : 10 높이 : 15
}
13-5. 예외 : 상속받지 않는 메서드들
파생 클래스는 부모 클래스의 대부분을 상속받지만, 예외도 있습니다.
- 부모 클래스의 생성자
- 부모 클래스의 연산자 오버로딩
- 부모 클래스의 프렌드 함수
13-6. 함수 오버라이딩
함수 오버라이딩(Overriding)은 부모 클래스에서 정의된 함수를 자식 클래스에서 덮어씌울 수 있는 기능을 말합니다.
ex) Shape 함수의 PrintInfo 함수를 덮어씌우기
void Shape::PrintInfo(){
std::cout << "모양의 이름: " << name_ << std::endl;
}
void Rectangular::PrintInfo(){
std::cout << "직사각형의 가로 : " << width_ << " 높이 : " << height_<< std::endl;
}
이런 식으로 네임스페이스를 파생클래스로 좁혀서 선언함으로써 파생클래스의 메서드를 재정의할 수 있습니다.
ex) 사용 예시
class Shape{
protected:
std::string name_;
public:
Shape(std::string name);
void PrintInfo();
};
class Rectangular: public Shape{
private:
int width_, height_;
public:
Rectangular(std::string name, int width, int height);
void PrintInfo();
};
int main(){
Rectangular rec = {"정사각형", 10, 15};
rec.PrintInfo(); // 직사각형의 가로 : 10 높이 : 15
rec.Shape::PrintInfo(); // 모양의 이름: 정사각형
}
물론, 부모 클래스의 메서드는 여전히 남아있기 때문에,
네임스페이스를 적절히 조절하여 부모 클래스의 메서드도 호출할 수 있습니다.
13-7. 파이썬 상속과 C++ 상속의 차이
파이썬에서의 상속
super
함수를 통해서 부모 클래스의 인스턴스를 직접 다룰 수 있음.- 여러번 상속되었거나, 다중상속의 경우 MRO(Method Resolution Order)에 의해서 부모 클래스의 인스턴스를 순서대로 호출할 수 있음.
C++에서의 상속
-
부모 클래스 인스턴스 자체를 다룰 수는 없음.
단지, 부모 클래스의 멤버들이 파생 클래스에 편입되는 방식일 뿐.
-
그렇기 때문에 연쇄적으로 부모 클래스의 멤버를 호출하는게 아니라, 이미 파생클래스의 멤버변수로 존재하므로 그냥 호출하면 됨.