Derived classes
모든 클래스 타입( class-key class 또는 struct 로 선언된)은 하나 이상의 기본 클래스 에서 파생 되도록 선언될 수 있으며, 이 기본 클래스들은 다시 자신의 기본 클래스에서 파생될 수 있어 상속 계층 구조를 형성합니다.
목차 |
구문
기본 클래스 목록은
base-clause
에 제공되며, 이는
클래스 선언 구문
에 포함됩니다.
base-clause
는 문자
:
로 시작하며, 하나 이상의
base-specifier
를 쉼표로 구분한 목록으로 구성됩니다.
| attr (선택 사항) class-or-computed | (1) | ||||||||
attr
(선택 사항)
virtual
class-or-computed
|
(2) | ||||||||
| attr (선택 사항) access-specifier class-or-computed | (3) | ||||||||
attr
(선택 사항)
virtual
access-specifier
class-or-computed
|
(4) | ||||||||
attr
(선택 사항)
access-specifier
virtual
class-or-computed
|
(5) | ||||||||
virtual
과
access-specifier
는 어떤 순서로든 나타날 수 있습니다.
| attr | - | (C++11부터) 임의 개수의 attributes 시퀀스 | ||||
| access-specifier | - | 다음 중 하나: private , public , 또는 protected | ||||
| class-or-computed | - |
다음 중 하나:
|
elaborated type specifier 는 문법적 제약으로 인해 class-or-computed 로 직접 나타날 수 없습니다.
|
base-specifier 가 base-clause 에서 pack expansions 일 수 있습니다.
|
(since C++11) |
만약 access-specifier 가 생략된 경우, class-key struct 로 선언된 파생 클래스의 경우 기본값이 public 이 되고, class-key class 로 선언된 파생 클래스의 경우 기본값이 private 이 됩니다.
struct Base { int a, b, c; }; // Derived 타입의 모든 객체는 Base를 하위 객체로 포함합니다 struct Derived : Base { int b; }; // Derived2 타입의 모든 객체는 Derived와 Base를 하위 객체로 포함합니다 struct Derived2 : Derived { int c; };
class-or-computed 로 표시된 클래스들은 base-clause 에 나열된 직접 기반 클래스들입니다. 이들의 기반 클래스들은 간접 기반 클래스입니다. 동일한 클래스는 직접 기반 클래스로 두 번 이상 지정될 수 없지만, 동일한 클래스가 직접 기반 클래스이면서 동시에 간접 기반 클래스일 수는 있습니다.
각 직접 및 간접 기본 클래스는 ABI에 종속된 오프셋에서 파생 클래스의 객체 표현 내에 기본 클래스 서브오브젝트 로 존재합니다. 빈 기본 클래스는 일반적으로 빈 기본 최적화 로 인해 파생 객체의 크기를 증가시키지 않습니다. 기본 클래스 서브오브젝트의 생성자들은 파생 클래스의 생성자에 의해 호출됩니다: 이러한 생성자들에 대한 인수들은 멤버 초기화 리스트 에서 제공될 수 있습니다.
가상 기본 클래스
각각의 구별된 기본 클래스가 virtual 로 지정될 때, 가장 파생된 객체 는 해당 타입의 기본 클래스 하위 객체를 단 하나만 포함합니다. 비록 클래스가 상속 계층에서 여러 번 나타나더라도 (매번 virtual 으로 상속되는 경우).
struct B { int n; }; class X : public virtual B {}; class Y : virtual public B {}; class Z : public B {}; // AA 타입의 모든 객체는 하나의 X, 하나의 Y, 하나의 Z, 그리고 두 개의 B를 가짐: // 하나는 Z의 기반이고 다른 하나는 X와 Y가 공유함 struct AA : X, Y, Z { AA() { X::n = 1; // 가상 B 부분 객체의 멤버를 수정 Y::n = 2; // 동일한 가상 B 부분 객체의 멤버를 수정 Z::n = 3; // 비가상 B 부분 객체의 멤버를 수정 std::cout << X::n << Y::n << Z::n << '\n'; // 223을 출력 } };
가상 기본 클래스를 사용한 상속 계층 구조의 예로는 표준 라이브러리의 iostreams 계층 구조가 있습니다: std::istream 과 std::ostream 은 가상 상속을 사용하여 std::ios 에서 파생됩니다. std::iostream 은 std::istream 과 std::ostream 모두에서 파생되므로, 모든 std::iostream 인스턴스는 std::ostream 서브객체, std::istream 서브객체, 그리고 단 하나의 std::ios 서브객체(그리고 결과적으로 하나의 std::ios_base )를 포함합니다.
모든 가상 기본 클래스 하위 객체는 비가상 기본 클래스 하위 객체보다 먼저 초기화되므로, 가장 파생된 클래스 만이 멤버 초기화 리스트 에서 가상 기본 클래스의 생성자를 호출합니다:
struct B { int n; B(int x) : n(x) {} }; struct X : virtual B { X() : B(1) {} }; struct Y : virtual B { Y() : B(2) {} }; struct AA : X, Y { AA() : B(3), X(), Y() {} }; // AA의 기본 생성자는 X와 Y의 기본 생성자를 호출하지만 // B가 가상 베이스이므로 해당 생성자들은 B의 생성자를 호출하지 않습니다 AA a; // a.n == 3 // X의 기본 생성자는 B의 생성자를 호출합니다 X x; // x.n == 1
가상 상속이 관련된 경우 클래스 멤버에 대한 비한정 이름 검색에는 특별한 규칙 이 있습니다 (때로는 지배 규칙이라고도 함).
Public 상속
클래스가 public 멤버 접근 지정자 를 사용하여 기본 클래스로부터 상속할 때, 기본 클래스의 모든 public 멤버는 파생 클래스의 public 멤버로 접근 가능하며, 기본 클래스의 모든 protected 멤버는 파생 클래스의 protected 멤버로 접근 가능합니다 (기본 클래스의 private 멤버는 friend로 지정되지 않는 한 절대 접근할 수 없습니다).
공개 상속은 객체 지향 프로그래밍의 하위 타입 관계를 모델링합니다: 파생 클래스 객체는 기본 클래스 객체입니다(IS-A 관계). 파생 객체에 대한 참조와 포인터는 해당 공개 기본 클래스 중 하나에 대한 참조나 포인터를 기대하는 어떤 코드에서도 사용 가능해야 합니다( LSP 참조). 또는 DbC 용어로 설명하면, 파생 클래스는 해당 공개 기본 클래스의 클래스 불변식을 유지해야 하며, 재정의하는 멤버 함수의 사전 조건을 강화하거나 사후 조건을 약화해서는 안 됩니다.
#include <iostream> #include <string> #include <vector> struct MenuOption { std::string title; }; // Menu는 MenuOption의 벡터입니다: 옵션을 삽입, 제거, 재정렬할 수 있습니다... // 그리고 제목을 가지고 있습니다. class Menu : public std::vector<MenuOption> { public: std::string title; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) std::cout << " " << (i + 1) << ". " << at(i).title << '\n'; } }; // 참고: Menu::title은 기본 클래스와 독립적인 역할을 하기 때문에 문제가 되지 않습니다. enum class Color { WHITE, RED, BLUE, GREEN }; void apply_terminal_color(Color) { /* OS-specific */ } // 이것은 나쁜 방법입니다! // ColorMenu는 모든 옵션이 사용자 정의 색상을 가지는 Menu입니다. class ColorMenu : public Menu { public: std::vector<Color> colors; void print() const { std::cout << title << ":\n"; for (std::size_t i = 0, s = size(); i < s; ++i) { std::cout << " " << (i + 1) << ". "; apply_terminal_color(colors[i]); std::cout << at(i).title << '\n'; apply_terminal_color(Color::WHITE); } } }; // ColorMenu는 공개 상속으로는 만족시킬 수 없는 다음과 같은 불변 조건이 필요합니다: // - ColorMenu::colors와 Menu는 같은 수의 요소를 가져야 합니다 // - 의미를 유지하려면 erase() 호출 시 colors에서도 요소를 제거해야 합니다, // 옵션이 자신의 색상을 유지할 수 있도록 하기 위해 // 기본적으로 모든 std::vector 메서드에 대한 비상수 호출은 ColorMenu의 불변 조건을 깨뜨리고 // 사용자가 colors를 올바르게 관리하여 수정해야 합니다. int main() { ColorMenu color_menu; // 이 클래스의 큰 문제는 ColorMenu::colors를 Menu와 동기화 상태로 유지해야 한다는 것입니다. color_menu.push_back(MenuOption{"Some choice"}); // color_menu.print(); // 오류! print()의 colors[i]가 범위를 벗어남 color_menu.colors.push_back(Color::RED); color_menu.print(); // OK: colors와 Menu가 같은 수의 요소를 가짐 }
보호 상속
클래스가 protected 멤버 접근 지정자 를 사용하여 베이스 클래스로부터 상속할 때, 베이스 클래스의 모든 public 및 protected 멤버들은 파생 클래스의 protected 멤버로 접근 가능합니다 (베이스 클래스의 private 멤버들은 friend로 지정되지 않는 한 절대 접근할 수 없습니다).
보호된 상속은 "제어된 다형성"을 위해 사용될 수 있습니다: Derived의 멤버들 내에서, 그리고 모든 추가로 파생된 클래스들의 멤버들 내에서, 파생 클래스는 IS-A 기반 클래스입니다: Base에 대한 참조와 포인터가 기대되는 곳에서 Derived에 대한 참조와 포인터가 사용될 수 있습니다.
프라이빗 상속
클래스가 private 멤버 접근 지정자 를 사용하여 베이스 클래스로부터 상속할 때, 베이스 클래스의 모든 public 및 protected 멤버들은 파생 클래스의 private 멤버로 접근 가능합니다 (베이스 클래스의 private 멤버들은 friend로 선언되지 않는 한 절대 접근할 수 없습니다).
프라이빗 상속은 정책 기반 설계에서 흔히 사용됩니다. 정책은 보통 빈 클래스이며, 이를 베이스 클래스로 사용하면 정적 다형성을 가능하게 하고 empty-base optimization 을 활용할 수 있기 때문입니다.
private 상속은 구성 관계를 구현하는 데에도 사용될 수 있습니다(기반 클래스 부분 객체는 파생 클래스 객체의 구현 세부 사항입니다). 멤버 사용은 더 나은 캡슐화를 제공하며, 파생 클래스가 기반의 protected 멤버(생성자 포함)에 접근해야 하거나, 기반의 가상 멤버를 재정의해야 하거나, 기반이 다른 기반 부분 객체보다 먼저 생성되고 나중에 파괴되어야 하거나, 가상 기반을 공유해야 하거나, 가상 기반의 생성을 제어해야 하는 경우를 제외하고 일반적으로 선호됩니다. 구성 구현을 위한 멤버 사용은 parameter pack 에서의 다중 상속이나 기반 클래스들의 식별자가 템플릿 메타프로그래밍을 통해 컴파일 시간에 결정되는 경우에도 적용할 수 없습니다.
보호된 상속과 유사하게, 비공개 상속 또한 제어된 다형성을 위해 사용될 수 있습니다: 파생 클래스의 멤버 내부에서(그러나 더 이상 파생된 클래스 내부에서는 아님), 파생 클래스는 IS-A 기본 클래스입니다.
template<typename Transport> class service : private Transport // Transport 정책으로부터의 private 상속 { public: void transmit() { this->send(...); // 제공된 transport를 사용하여 전송 } }; // TCP transport 정책 class tcp { public: void send(...); }; // UDP transport 정책 class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // TCP를 통해 전송
멤버 이름 조회
클래스 멤버에 대한 비한정 및 한정 이름 조회 규칙은 이름 조회 에서 자세히 설명합니다.
키워드
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 1710 | C++98 |
class-or-decltype
구문이
template 디스앰비귀에이터가 필요한 종속 클래스로부터 파생하는 것을 불가능하게 함 |
template 허용 |