상속은 동적 메모리 대입(동적 할당, 해제)과 어떤 관계를 맺을까? 정답은 파생 클래스의 성질에 따라 다르다.
아래와 같은 기초 클래스가 있다고 가정해보자.
class BaseClass
{
public:
BaseClass(const char* l = "null", int r = 0); //생성자
BaseClass(const BaseClass& rs); //복사 생성자
virtual ~BaseClass(); //소멸자
BaseClass& operator=(const BaseClass& rs); //대입 연산자
};
위 기초 클래스를 파생하는 클래스의 상황에 따라 취할 수 있는 조치에 대해 알아보자.
case 1 - 파생 클래스가 new를 사용하지 않는다.
위 클래스를 파생하는 파생 클래스는 new를 사용하지 않고 다른 특별한 처리도 요구하지 않는다고 가정해보자. 파생 클래스를 위해 명시적 소멸자, 복사 생성자, 대입 연산자를 정의해야 하는가? 정답은 아니오다.
우선 소멸자의 경우, 명시적으로 소멸자를 정의하지 않으면, 컴파일러는 기본 소멸자를 정의한다. 파생 클래스의 소멸자는 자신의 코드를 수행한 후에 기초 클래스의 소멸자를 항상 호출한다. 특별한 동작을 요구하지 않는다고 가정했기에 기본 소멸자로 충분하다.
복사 생성자의 경우, 기본 복사 생성자는 멤버별 복사를 한다. 멤버별 복사는 동적 메모리 대입에는 사용할 수 없고, 새로운 파생 클래스의 멤버에 대해서는 멤버별 복사를 사용할 수 있다. 하지만 아직 객체의 문제가 남아있다. 클래스 멤버 또는 기초 클래스의 클래스 성분을 복사하는 것은 해당 클래스의 복사 생성자를 통하여 이루어진다. 파생 클래스의 기본 복사 생성자는 기초 클래스 성분을 복사하기 위해 명시적인 기초 클래스의 복사 생성자를 이용하므로, 파생 클래스의 기본 복사 생성잔느 새로운 파생 클래스 멤버에 대해 사용할 수 있고, 상속받은 기초 클래스 객체에 대해서도 사용할 수 있다.
대입 연산자 역시 복사 생성자와 같은 논리가 적용된다. 기초 대입 연산자는 기초 클래스 성분의 대입을 위해 기초 클래스의 대입 연산자를 사용하므로 기초 대입 연산자만 있으면 충분하다.
case 2 - 파생 클래스가 new를 사용한다.
파생 클래스가 new를 사용한다고 가정해보자. 이 경우엔 파생 클래스를 위한 명시적 소멸자, 복사 생성자, 대입 연산자를 정의해야 한다.
class DerivedClass : public BaseClass
{
public:
~DerivedClass(); //소멸자
DerivedClass(const DerivedClass& rh); //복사 생성자
DerivedClass& operator=(const DerivedClass& rh); //대입 연산자
private:
char* style;
};
DerivedClass::~DerivedClass() //소멸자
{
delete[] style;
}
DerivedClass::DerivedClass(const DerivedClass & rh) //복사 생성자
: BaseClass(rh)
{
style = new char[strlen(rh.style) + 1];
strcpy(style, rh.style);
}
DerivedClass& DerivedClass::operator=(const DerivedClass & rh) //대입 연산자
{
if (this == &rh)
return *this;
BaseClass::operator=(rh); //rh의 기초 클래스 복사
delete[] style;
style = new char[strlen(rh.style) + 1];
strcpy(style, rh.style);
return *this;
}
기초 클래스와 파생 클래스가 둘 다 동적 메모리 대입, 파생 클래스 소멸자, 복사 생성자, 대입 연산자를 사용할 때, 모두 기초 클래스 성분을 처리하기 위해 그들의 기초 클래스 대응물을 사용해야 한다.
소멸자의 경우 자동으로 이뤄지고, 생성자의 경우 멤버 초기화 리스트를 통해 기초 클래스 복사 생성자를 호출하거나 기본 생성자가 자동으로 호출된다. 대입 연산자는 기초 클래스 대입 연산자의 명시적 호출에서 사용 범위 결정 연산자를 사용함으로써 이뤄진다.
동적 메모리 대입과 프렌드를 사용하는 상속
파생 클래스들이 기초 클래스의 프렌드에 어떻게 접근할까?
class BaseClass
{
public:
...
friend std::ostream& operator<<(std::ostream& os, const BaseClass& rs);
private:
char* label;
int rating;
};
class DerivedClass : public BaseClass
{
public:
...
friend std::ostream& operator<<(std::ostream& os, const DerivedClass& rh);
private:
char* style;
};
std::ostream& operator<<(std::ostream& os, const BaseClass& rs)
{
os << "상표 : " << rs.label << endl;
os << "등급 : " << rs.rating << endl;
return os;
}
std::ostream& operator<<(std::ostream& os, const DerivedClass& rh)
{
os << (const BaseClass&)rh; //강제 데이터형 변환
os << "스타일 : " << rh.color << endl;
return os;
}
파생 클래스의 friend의 경우 DerivedClass의 프렌드이기 때문에 style 멤버에 접근이 가능하지만, 기초 클래스에 대해서는 friend가 아니므로 기초 클래스의 label과 rating 멤버에 접근할 수가 없다. 이를 해결하기 위해 기초 클래스에도 friend를 사용한다. 하지만 또 하나의 문제는, friend는 멤버 함수가 아니기 때문에, 범위 결정 연산자를 사용할 수가 없다. 따라서 캐스팅을 사용하여 강제 데이터형 변환을 사용해야 한다.
'서적 정리 > C++ 기초 플러스' 카테고리의 다른 글
3.프로그램 작성 요령 (0) | 2022.07.20 |
---|---|
2.이식성과 표준 (0) | 2022.07.20 |
1.C++의 탄생: 간략한 역사 (0) | 2022.07.20 |
81.클래스 설계 복습 (0) | 2022.07.19 |
79.추상화 기초 클래스 (0) | 2022.07.14 |
78.접근제어: protected (0) | 2022.07.13 |
77.정적 결합과 동적 결합 (0) | 2022.07.13 |
76.public 다형 상속 (0) | 2022.07.12 |
댓글