4 분 소요

! 틀린 내용이 있을 수 있습니다. 틀린 내용이 있다면 댓글 부탁드립니다.

모든 테스트는 visual studio 22 (v143) 으로 진행했습니다.

개요

c++ 클래스의 상속에 대해서 사용방법과 실제 메모리가 어떻게 되는지 대해서 설명한다.

상속이란

정의

특정 클래스는 다른 클래스의 멤버들을 특정 조건으로 받아서 사용할 수 있다. 이를 “상속( inheritance )” 라고 한다.
상속 받은 class를 derived class, 상속 하는 class를 Base class라고 부른다.
일반적으로 OOP에서 객체들을 구조화할 때 많이 사용된다.

문법

class Derived : [virtual] [access-specifier] Base
{
   // member list
};
class Derived : [virtual] [access-specifier] Base1,
   [virtual] [access-specifier] Base2, . . .
{
   // member list
};

access-specifier

public, protected, private 3가지 중 하나이다.
상속받은 class ( derived class )는 가장 높은 access-specifier의 접근제어 수준을 가진다.

public -> protected -> private 순서로 접근제어 수준이 높아진다.

class Base {
public:
	int a;
};

class Derived : private Base { }; // a is private in Derived

class Derived2 : public Derived {
public:
	void print() {
		cout << a << endl; // error
	}
};

위의 예시를 보면 int a;Base에서는 public 이지만 상속 받을 때 private으로 상속 받았기 때문에 Derived에서는 private 으로 정의된다.

다중 상속

여러개의 Base class를 가질 때는 , 로 구분해서 상속 받을 수 있다.

상속 종류

단일 상속 ( single inheritance )

개요

일반적인 상속을 뜻한다. 하나의 Base class 만을 가지는 상속을 뜻한다.

예를 들어 Shibainu ( 시바견 ) 라는 class를 생각해보자.
ShibainuDog이고 DogAnimal이다. 이를 상속관계로 표현하면 이렇게 표현할 수 있다.

class Animal { };

class Dog : public Animal { };

class Shibainu : public Dog { };

이럴 때 DogShibainudirect inheritance class 라고 부르고, Animalindirect inheritance class 라고 부른다.

조건

Base class는 Derived class 보다 앞에 정의 되어있어야한다. 즉, Base class는 complete class 여야한다.

왜 그런지는 Derived 가 만들어지기 위해서는 Base 가 먼저 만들어져야하기 때문일 것이다. ( 정확하지 않음 )

Ambiguous

일반적으로 접근할 때 이름이 다르면 이름으로 접근하면 되지만 멤버의 이름이 같은 경우가 있다. 이럴 때는 정확하게 class::member 로 접근해서 모호함을 해결 할 수 있다.

class Base {
public:
	int a;
};

class Derived : public Base {
public:
	int a;
};

class Derived2 : public Derived {
public:
	void test() {
		a = 10; // ambiguous. Base::a or Derived::a?
      Base::a = 10;
      Derived::a = 12;
	}
};

메모리 구조

c++ 표준에서는 클래스를 상속 받을 때의 메모리 구조를 보장하고 있지 않다. 구현의 문제이고, 컴파일러마다 다르게 작동할 수 있다.

일반적으로는 Base class가 Derived class 앞에 존재하는 형식으로 구현되어있다.

class Base {
public:
	virtual ~Base() {};
	int a;
};

class Derived : public Base {
public:
	int b;
};

class Derived2 : public Derived {
public:
	int c;
};

위와 같은 구조에서 Derived2를 만들기 위해서 Base는 맨 위에 생성되고 그 밑에 Derived, Derived2 순서로 생성된다.

memory-layout

위의 사진처럼 align이 8byte로 고정되고 뒤로 4byte 씩 추가되는 것을 볼 수 있다.

vptr의 위치

visual studio 22(v143)에서는 vptr 의 위치가 항상 앞으로 고정된다.

예를 들어서 이런 경우를 보자.

class Base {
public:
	int a;
};

class Derived : public Base {
public:
	virtual ~Derived() {};
	int b;
};

class Derived2 : public Derived {
public:
	int c;
};

이렇게 되어있다면, 처음 생각드는 것은 Base::a / Derived::vptr , Derived::b / Dervied2::c 이렇게 되어있는 것을 생각할 것이다. 하지만 실제로 visual studio 에서는 Derived::ptr이 제일 앞에 있고 Base::a 부터 만들어진다.

memory-layout2

사진처럼 맨 앞에 Derived::ptr이 있고, 뒤에 align 되어있는 것을 확인할 수 있다.

다중 상속 ( multiple inheritance )

만악의 근원이다.

개요

최신 언어들과는 다르게 c++에서는 다중상속을 지원한다. 상속을 여러개의 Base class에서 받는 상속이다.

예를 들어 MeMotherFather의 특징들을 물려받았다. 그렇다면 이렇게 표현할 수 있다.

class Mother {};

class Father {};

class Me : public Mother, public Father {};

주의

Me를 만들기 위해서는 Mother를 만들고, Father를 만들게 된다. ( 순서가 선언에 의해 고정되어 있다. )
Me를 파괴할 때는 반대로 Father를 파괴하고 Mother를 파괴한다. ( 순서가 선언에 의해 고정되어 있다. )

class 선언에서 상속 순서는 메모리 레이아웃에 영향을 미칠 수 있다. 예상하지 못한 행동을 하지 않도록 주의해야한다.

virtual

위의 예시는 사실 Mother 도 Father 도 Human 이기 때문에 아래와 같이도 생각할 수 있다.

class Human {};

class Mother {};

class Father {};

class Me : public Mother, public Father {};

이러면 앞에서 설명한대로 Me를 생성하면 Human + Mother를 생성하고, Human + Father를 생성한다. 그렇게 되면 Human이 두 개가 생성되는 문제가 생긴다. 그리고 이러한 구조를 죽음의 다이아몬드 라고 부른다.

        Human
       /      \
      V        V
  Mother        Father
       \      /
        V    V
          Me

이때의 메모리 구조는 이런식이 된다.
value-category

이 때 두 개를 생성하지 않고 하나만 생성하게 해주는 것이 virtual 키워드이다.

각 Base class마다 virtual 을 선언해줘야해서 virtual이 선언된 class 끼리는 하나로 통일되지만 virtual이 선언되지 않은 상속은 여전히 생성을 한다.

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};
class Derived3 : public Base {};
class DerivedD : virtual public Derived1, virtual public Derived2, public Derived3 {};

이런 구조라면 Base는 2개가 만들어진다.

이론은 이런데… 실제로 메모리 구조를 확인해보면 각각 생성된다. 왜 그런지는 잘 모르겠습니다. 댓글 부탁드립니다.

Ambiguous

단일 상속과 비슷하게 모호한 멤버로 접근할 때 Parent::Base::member 처럼 정확한 클래스 위치로 접근하면 모호함을 해결할 수 있다.

결론

이 포스팅으로 class를 상속할 때 어떻게 하는지, 상속의 종류, 각 상속의 특징에 대해서 알아보았다. 이 외에도 더 많은 상속에 관련된 자세한 내용이 있으나 짧은 식견 이슈로 더 자세한 내용은 다른 정리된 내용을 참고하면 좋을 거 같다.

참조

Microsoft Learn - Inheritance Overview
Microsoft Learn - Single Inheritance
Microsoft Learn - Multiple Inheritance

댓글남기기