상속
상속이란 기반 클래스(base class)의 속성과 기능을 파생 클래스(derived class)에 물려주는 것이다. 즉 파생 클래스에서는 기반 클래스의 멤버 변수와 멤버 함수를 사용할 수 있다. 단 기반 클래스의 멤버 변수와 멤버 함수의 접근지정자가 private
이면 파생 클래스에서 접근이 안된다.
상속을 통해서 기반 클래스에 있는 멤버 변수와 멤버 함수를 그대로 사용할 수 있으니 파생 클래스에서는 멤버 변수와 함수를 간결하게 작성할 수 있고, 클래스들 간 구조적 관계를 파악하기도 용이하다는 장점이 있다. 또한 클래스 재사용도 쉬워진다.
클래스 상속 기본 문법은 다음과 같다.
class DerivedName : accessmodifier BaseName {
....
};
accessmodifier 는 상속 접근 지정자로 기반 클래스의 멤버를 가져올 때 접근 지정자를 설정하는 것이다.
상속 접근 지정자
상속 접근 지정자는 기반 클래스의 멤버에 대한 접근 지정자를 설정하는 것이다. 그러나 상속 접근 지정자로는 접근을 막을 수는 있어도 접근을 열수는 없다. 즉 아래 표와 같이 상속 형태가 정해진다.
상속 접근 지정자 | 기반 클래스 | 파생 클래스 |
public | public | public |
protected | protected | |
private | private | |
protected | public | protected |
protected | ||
private | private | |
private | public | private |
protected | ||
private |
즉 public
을 protected
혹은 private
로 만들거나 protected
를 private
로 만드는 것만 상속 접근 지정자를 통해 가능하다.
상속 관계의 생성자와 소멸자
상속받아 만들어진 파생 클래스의 객체가 생성되면 파생 클래스의 생성자만 실행되는 것이 아니라 기반 클래스의 생성자 역시 실행된다. 예를 들어 아래와 같은 클래스 관계가 있다 가정하자.
#include <iostream>
using namespace std;
class Deep {
public:
Deep() {
cout << "Deep" << endl;
}
~Deep() {
cout << "~Deep" << endl;
}
};
class Moderate : public Deep {
public:
Moderate() {
cout << "Moderate" << endl;
}
~Moderate() {
cout << "~Moderate" << endl;
}
};
class Shallow : public Moderate {
public:
Shallow() {
cout << "Shallow" << endl;
}
~Shallow() {
cout << "~Shallow" << endl;
}
};
main 함수를 만들어 Shallow 객체를 생성하고 종료하면 다음과 같이 출력된다.
Deep
Moderate
Shallow
~Shallow
~Moderate
~Deep
즉 기반 클래스의 생성자와 소멸자가 모두 실행되며, 생성자는 가장 깊은 기반 클래스의 생성자부터 실행되고, 소멸자는 가장 얕은 파생 클래스의 소멸자부터 실행된다.
기반 클래스 생성자
위 경우처럼 파생 클래스에서 따로 기반 클래스 생성자를 호출하지 않는다면 기반 클래스의 생성자 중 매개변수가 없는 생성자가 실행되고, 생성자가 없다면 기본 생성자를 컴파일시에 선언하여 실행한다. 그런데 이때 기반 클래스가 매개변수가 있는 생성자만을 가지고 있을 수 있다. 그렇다면 파생 클래스에서 생성자를 실행할 때 기반 클래스 생성자가 실행되지 않으므로 컴파일 오류가 발생한다.
따라서 기반 클래스 생성자를 명시적으로 호출해야할 때가 있다. 이때는 다음과 같이 호출한다.
class DerivedName : accessmodifier BaseName {
....
DerivedName(parameter) : BaseName(parameter) {
....
}
....
};
즉 :
을 통해 기반 클래스의 생성자를 명시적으로 호출해준다. 이때 매개변수를 전달해줄 수 있고, 따라서 매개변수가 있는 생성자만을 가진 기반 클래스를 사용할 수 있게 된다.
다중 상속
말 그대로 여러 클래스를 상속받는 것을 말한다. 기본 문법은 아래와 같다.
class DerivedName : accessmodifier BaseName1, accessmodifier BaseName2 {
....
};
그런데 다중 상속은 문제를 발생시킬 여지가 있다. 예를 들어 아래와 같은 상속관계가 있다고 해보자.
#include <iostream>
using namespace std;
class Deep {
protected:
int data;
public:
Deep() {}
};
class Moderate1 : public Deep {
protected:
int data1;
public:
Moderate1() {}
};
class Moderate2 : public Deep {
protected:
int data2;
public:
Moderate2() {}
};
class Shallow : public Moderate1, public Moderate2 {
public:
Shallow() {}
void setData(int data) {
this->data = data;
}
};
이때 Shallow 객체를 선언하고 setData를 이용하여 data를 10으로 초기화한다 해보자. 근데 이때 data가 Moderate1이 가진 Deep의 data인지 아니면 Moderate2가 가진 Deep의 data인지 알 방법이 없고, 보통 이런 상속이 있다면 위 코드에서는 data1, data2 와 같은 멤버를 같이 사용하기 위해서 다중 상속을 받지, Deep 두 개를 이용하기 위해서 다중 상속을 받지 않기에 문제가 발생한다. 즉 중복되어 상속이 이뤄지는 클래스가 있다면 이를 처리할 필요가 있다.
이를 가상 상속을 통해 해결한다. 파생 클래스에서 기반 클래스를 선언할 때 virtual
을 사용하는 것이다. 예를 들어 위 코드에서 중복 상속 문제를 막기위해 수정한다면 다음과 같이 수정할 수 있다.
#include <iostream>
using namespace std;
class Deep {
protected:
int data;
public:
Deep() {}
};
class Moderate1 : virtual public Deep {
protected:
int data1;
public:
Moderate1() {}
};
class Moderate2 : virtual public Deep {
protected:
int data2;
public:
Moderate2() {}
};
class Shallow : public Moderate1, public Moderate2 {
public:
Shallow() {}
void setData(int data) {
this->data = data;
}
};
이렇게 하면 Deep은 하나만 생성되고, 따라서 Shallow에서 Deep의 멤버를 사용할 때 문제가 생기지 않는다.
참고로 다중 상속을 하면 상속된 순서대로 생성자가 실행된다. 즉 Moderate1이 Moderate2 보다 먼저 생성자가 실행된다.
'Language > C & C++' 카테고리의 다른 글
[C++] virtual 키워드를 활용한 함수 오버라이딩 및 추상 클래스 (0) | 2024.11.13 |
---|---|
[C++] 업캐스팅(upcasting) 및 다운캐스팅(downcasting) (0) | 2024.11.13 |
[C++] 연산자 오버로딩(operator overloading) (0) | 2024.10.27 |
[C++] 프렌드(friend)를 통한 클래스 멤버 접근 (0) | 2024.10.27 |
[C/C++] 헤더파일 분할 작성과 헤더파일 중복 선언 방지 (0) | 2024.10.13 |