주의 : 이 글은 C의 기초문법에 대해 상세하게 다루지 않습니다. (즉 C언어에서 배울 수 있는 기초내용은 생략합니다)
※ 명품 C++ Programming 의 책을 참고하여 개인적으로 정리한 글입니다.
이 글의 목적은 해당 책의 내용을 인용하여 더 쉽게 이해하고자 정리하고, 더 쉬운 예제를 통해 이해하는 것을 목표로 하고 있습니다.
명품 C++ Programming의 예제문제와 실습문제가 정말 좋으므로, 깊게 공부하고 싶다면 책을 구매하는 것을 추천드립니다.
책의 저작권 등등 각종 권한은 출판사와 지은이/옮긴이에 있습니다.
- 출판사: (주)생능 출판사
- 지음: 황기태
상속 (Inheritance)
상속이란 뭘까? 그냥 장황하게 이해하지 말고 코드로 보자.
클래스 스마트폰{
이름 = " "
스펙 = " "
}
클래스 아이폰 상속 스마트폰 {
제조사 = "애플"
특징 = "애플페이"
}
클래스 아이폰14pro 상속 아이폰 {
아이폰14pro 생성자(){
이름 = "아이폰14pro"
스펙 = "빠름"
}
}
클래스 아이폰14 상속 아이폰 {
아이폰14 생성자(){
이름 = "아이폰14"
스펙 = "중간"
}
}
상속을 받을 경우 상위 클래스에 있는 멤버변수와 메소드가 자식 클래스 내부로 들어온다고 생각하면된다. 만약에 상속이 없다면 이 코드는 어떻게 변화할까?
클래스 아이폰14pro {
이름 = " "
스펙 = " "
제조사 = "애플"
특징 = "애플페이"
아이폰14pro 생성자(){
이름 = "아이폰14pro"
스펙 = "빠름"
}
}
클래스 아이폰14 {
이름 = " "
스펙 = " "
제조사 = "애플"
특징 = "애플페이"
아이폰14 생성자(){
이름 = "아이폰14"
스펙 = "중간"
}
}
지금은 극단적으로 클래스의 내용을 단순하게 적어서 그렇지만 똑같은 내용이 반복되는 코드가 생기게되었다. 그리고 아이폰 15가 출시되었다면 어떨까? 우리는 아이폰 14의 코드로 가서 클래스를 복사하고 다음처럼 넣을 것이다.
클래스 아이폰15 {
이름 = " "
스펙 = " "
제조사 = "애플"
특징 = "애플페이"
아이폰15 생성자(){
이름 = "아이폰15"
스펙 = "중간보단 빠름"
}
}
아이폰 클래스를 상속받으면 이런 문제가 해결된다.
클래스 아이폰15 상속 아이폰 {
아이폰15 생성자(){
이름 = "아이폰15"
스펙 = "중간보단 빠름"
}
}
그래서 상속의 장점을 적어보면
1. 간결한 클래스 작성이 가능하다.
2. 클래스 간의 계층적 분류 및 관리의 용이함
3. 클래스 재사용과 확장을 통한 소프트웨어 생산성 향상
4. 주의할 점은 연관성이 없는 클래스에서 상속을 받으면 안된다.
상속 (Inheritance)을 해보자.
일단 아래 코드는 틀렸다.
#include <iostream>
using namespace std;
class animal {
string name;
int age;
public:
animal() {}
void new_year() {
age++;
}
};
class dog : public animal {
public:
dog() {
name = "dog";
age = 0;
}
};
int main() {
dog d;
}
animal 클래스를 상속한 dog 클래스 class dog : public animal 이런 느낌으로 자식 클래스를 선언할 수 있다. 당연히 public에는 protected 가 들어갈 수도 있고, private가 들어갈 수도 있다.
그건 그렇고 위의 코드가 틀린 이유가 무엇일까?
정답은
부모클래스에서 변수를 private로 자식들도 접근을 못하게 숨겨놓았기 때문이다. 그래서 자식클래스에서만 사용할 예정이라면 protected로 선언을 해주어야 한다. 아니면 public으로 설정을 할 수도 있다.
가장 좋은 방법은 set_value 형식의 메소드를 부모클래스에 만드는 것이라 볼 수 있다.
#include <iostream>
using namespace std;
class animal {
string name = "알";
int age = 0;
protected:
void set_name_age(string name, int age) {
this->name = name;
this->age = age;
}
public:
animal() {}
void new_year() {
age++;
}
string get_name() {
return name;
}
int get_age() {
return age;
}
};
class dog : public animal {
string sound = "멍멍";
public:
dog() {
set_name_age("dog", 0);
}
string get_sound() {
return sound;
}
};
다음처럼 부모클래스의 변수를 변경할 수 있는 함수는 protected로 상속된 클래스만 접근할 수 있게 하면될 것이다. 추가로 변수의 정보를 출력해줄 수 있는 느낌은 public으로 들어가는게 좋다. 이렇게 생성을 하면 객체지향의 캡슐화를 지키는 코드를 작성할 수 있을 것이다.
상속의 업캐스팅(up-casting)
자식 클래스의 객체를 부모클래스의 포인터로 가리키는 것을 말한다. 혹은 자식 클래스의 객체가 부모 클래스 타입으로 형변환 되는 것을 말합니다. 그냥 가장 쉽게 코드부터 봅시다. 위의 코드의 main문만 수정해봅시다.
int main() {
dog mung;
dog *pKid = &mung;
animal* pParent = pKid; // 업캐스팅
cout << "부모 클래스 : " << pParent->get_name() << " " << pParent->get_age();
cout << "자식 클래스 : " << pParent->get_sound();
}
이 코드가 어려우면 아래의 코드를 보면됩니다.
int main() {
animal ani;
dog mung;
ani = mung; // 업캐스팅
cout << "부모 클래스 : " << ani.get_name() << " " << ani.get_age();
cout << "자식 클래스 : " << ani.get_sound();
}
ani = mung 에서 업캐스팅이 일어난 상태입니다. 그러나 ani 객체의 자식 객체의 초기화된 멤버변수를 가지고 있습니다.
그러나 자식 클래스에서 새롭게 선언한 sound의 멤버 변수나 , 멤버 함수에는 접근할 수가 없다.
업스케일링은 객체지향에서 중요한 개념이다. 잘 이해를 하라는데, 교재는 좀 어렵고 인터넷에 검색하면 쉽게 풀어서 설명해준 사이트가 많다.
상속의 다운캐스팅(down-casting)
업캐스팅과 반대로 부모 클래스 포인터가 가리키는 객체를 자식 클래스의 포인터로 가리키는 것을 뜻합니다. 업캐스팅과는 반대로 다운캐스팅에는 항상 명시적으로 타입 변환을 지정해주어야한다. 또 다른 블로그에서는 업캐스팅된 포인터를 다시 원래대로 되돌리는 작업이라고도 표현한다.
이거는 안된다.
int main() {
dog mung;
dog *pKid = &mung;
animal* pParent = pKid;
cout << "부모 클래스 : " << pParent->get_name() << " " << pParent->get_age();
pKid = (dog *)pParent;
cout << "자식 클래스 : " << pKid->get_sound();
}
이런식으로 해주어야한다.
상속에서의 생성자와 소멸자
- 상속된 객체를 생성하면 부모클래스의 생성자와 자식클래스의 생성자가 동시에 실행된다. 순서는 부모클래스의 생성자가 먼저 실행된다. 만약 부모클래스의 생성자가 중복된 경우 이를 코드상에서 지정하지 않으면, 기본생성자를 호출한다.
- 소멸자의 경우에는 생성자의 실행과 반대방향으로 실행된다.
기본생성자가 없는 경우 어떻게되는지 한 번 코드상에서 확인해보자.
기본 생성자가 없기 때문에 실행이 되지 않는다. 그렇기 때문에 상속된 클래스에서는 명시적으로 생성자를 선택해주어야한다. 다음처럼 코드를 작성한다.
매개변수에 따라서 적절한 생성자를 따라갈 것이다.
상속의 종류
앞서 살펴본 바와 같이 상속하기전에 public이나 protected 혹은 private 같은 접근지정자를 선언으로 했는데, 상속에서는 기본 클래스에 선언된 멤버들의 접근 지정을 변경하는 과정이다.
public 상속 : 기본 클래스의 protected, public 멤버들은 접근 지정 변경없이 자식클래스에 상속된다.
protected 상속 : 기본 클래스의 protected, public 멤버들은 모두 protected 접근 지정으로 변경되되어 자식클래스에 상속된다.
private 상속 : 기본 클래스의 protected, public 멤버들은 모두 private 접근 지정으로 변경되어 파생 클래스에 상속 확장된다.
만약 상속의 종류를 빼먹었다면 private로 디폴트로 처리된다.
다중상속
이는 자바에서는 불가능하나 C++에서는 가능한 방법으로 한 번에 여러 곳에서 상속을 받는 개념이다. 그냥 다음처럼 작성하면된다.
class dog_leg : public animal, public dog
이런 느낌으로
가상상속
여러개를 상속받으면 여러모로 효율적일 수 있으나, 여러 문제점으로 인해 Java에서는 이를 지원하지 않는다. 그 문제점이 무엇인지 확인을 해보자.
class 신{
public:
string 외모
};
class 동물 : public 신{
public:
age 나이
};
class 지능 : public 신{
public:
age 지능
};
class 강아지 : public 동물, public 지능{
머시기
}
이 상황에서 강아지 객체의 외모 멤버변수는 어디서 부터 온 것인가? 동물 클래스에서 온 외모인가? 지능 클래스에서 온 외모인가? 이를 알 수가 없고 모호함의 문제가 발생한다.
그래서 이러한 멤버 중복 생성 문제를 해결하기 위해 가상상속의 개념이 등장했다.
class 신{
public:
string 외모
};
class 동물 : virtual public 신{
public:
age 나이
};
class 지능 : virtual public 신{
public:
age 지능
};
class 강아지 : public 동물, public 지능{
머시기
}
이렇게 가상 상속을 진행하면 외모에 할당된 공간을 공유하기 때문에 문제가 사라진다.
참고 문헌
Scott Douglas Meyers(2015). Effective C++. Protec Media(프로텍 미디어)
Robert C. Martin(2021).UML 실전에서는 이것만 쓴다(UML for Java Programmers). 인사이트
황기태(2021). 명품 C++ Programming. (주)생능출판사
'ㅇ 공부#언어 > (C++)기초' 카테고리의 다른 글
(기초 C++) 10장.템플릿과 STL(표준 템플릿 라이브러리) (0) | 2023.03.07 |
---|---|
(기초 C++) 9장. C++의 가상 함수와 추상 클래스 (0) | 2023.03.06 |
(기초 C++) 7장. C++에서의 프랜드와 연산자 중복 (0) | 2023.03.01 |
(기초 C++) 6장. C++에서의 함수 중복과 static 멤버 (0) | 2023.02.26 |
(기초 C++) 5장. C++에서의 함수의 참조와 복사 생성자 (0) | 2023.02.26 |