본문으로 바로가기
반응형

상속

클래스 확장

상속의 사전적인 의미는 부모의 모든 것을 자식에게 물려주는 것인데 OOP의 상속도 비슷하다.

상속은 이미 정의한 클래스의 멤버를 물려받아 새로운 클래스를 정의하는 기법이다.

- 기존 클래스 재활용

- 공통 부분을 상위 클래스에 통합하여 반복 제거

- 공동의 조상을 가지는 클래스 계층을 형성하여 다형성 구현

 

클래스를 아무리 잘 설계해도 요구 사항은 끊임없이 변하고, 환경이 수시로 바뀌기 때문에 기능을 추가, 변경해야하는 경우가 빈번하다. 기존 클래스를 수정해버리면 이 클래스를 사용하는 기존의 코드가 영향을 받기 때문에 원래 클래스는 유지하고 확장된 클래스를 새로 만들어야한다.

 

has a 

철수는 책을 가지고있다(o)

책은 철수를 가지고있다(x)

 

is a

철수는 사람이다(o)

사람은 철수다(x)

 

has a 관계나 is a 관계가 성립하면 상속관계가 성립하게된다.

 

 

상속의 예시

기존 클래스를 상속하여 새로운 클래스를 정의하는 문법은 다음과 같다.

상속 액세스 지정자는 밑에서 알아보기로하고 일단은 public을 사용한다.

 

새로 선언할 클래스의 이름 뒤에 :과 부모 클래스를 지정한다. 이 지정에 의해 컴파일러는 부모클래스의 모든 멤버를 자식 클래스로 복사하며 {} 괄호 안에 자식이 추가할 멤버를 선언하면 된다.

상속 관계에 있는 두 클래스는 언어에 따라 사용하는 명칭이 조금씩 다르다.

원본 클래스 : 기반(base), 슈퍼, 부모
새 클래스 : 파생(derived), 서브, 자식

Human이라는 클래스로부터 Student 클래스를 파생시켜보자.

// 파일이름 : InheritStudent.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

// 부모 클래스 선언
class Human
{
private:
	char name[12];
	int age;

public:
	Human(const char* name, int age)
	{
		strcpy(this->name, name);
		this->age = age;
	}

	void intro()
	{
		cout << "이름 : " << this->name << endl;
		cout << "나이 : " << this->age << endl;
	}
};


// 자식 클래스 선언
class Student : public Human  // : 를 통해 상속시켜주는것임
{
private:
	int stunum;

public:
	Student(const char* name, int age, int stunum) : Human(name, age)  // 콜론 초기화
	{
		this->stunum = stunum;
	}

	void study()
	{
		cout << "학번 : " << this->stunum << endl;
		cout << "22는 4, 23은 6, 24 8" << endl;
	}
};

int main()
{
	Human kim("김길동", 29);
	kim.intro();

	Student hong("홍길동", 15, 123456);  // 기존의 클래스를 재사용하면서 123456만 처리하는 클래스를 새로 만듬 -> 재사용성, 상속
	hong.intro();   // Human으로부터 상속받아서 intro를 사용할 수 있음.
	hong.study();
}

Student hong; 을 선언했을때 순서

1. 파생클래스의 생성자가 호출

2. 부모클래스의 생성자 호출 및 실행(클론 초기화때문에 먼저 실행되는것임)

3. 파생클래스의 생성자 실행

 

상속받은 멤버는 반드시 초기화 리스트에서 부모의 생성자로 전달하여 초기화를 위임한다.

부모의 생성자가 여러 개로 오버로딩 되어있다면 초기화 리스트의 인수 목록에서 어떤 생성자를 호출할 것인지 선택한다.

 

상속과 정보 은폐

부모 클래스의 모든 멤버는 자식 클래스에 상속된다. 상속받는 것과 마음대로 사용하는것은 다른 문제이다. 자식은 부모의 멤버를 소유하지만 부모가 허락하지않은 멤버를 마음대로 읽을 수는 없다.

 

private 멤버는 오로지 자기 자신만 사용할 수 있다. 상속관계의 자식 클래스도 외부로 간주되어 private 멤버를 액세스할 수 없다.

아무리 자식이라해도 부모의 숨겨진 멤버를 마음대로 건드릴 수 없다. 그런데 자식 클래스는 부모 클래스와 어느 정도 관련이 있기때문에 쌩판 남은 아니다. 외부에 대해서는 숨기더라도 자식에 대해서는 액세스를 허용할 필요가 있다.

이럴때 사용하는 접근지정자 protected는 상속관계일때 중간 단계 액세스 지정자로 사용한다.

// 파일이름 :  InhritAccess.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

// 부모 클래스 선언
class Human
{
private:
	char name[12];
	int age;

public:
	Human(const char* name, int age)
	{
		strcpy(this->name, name);
		this->age = age;
	}

	void intro()
	{
		cout << "이름 : " << this->name << endl;
		cout << "나이 : " << this->age << endl;
	}
};


// 자식 클래스 선언
class Student : public Human  // : 를 통해 상속시켜주는것임
{
private:
	int stunum;

public:
	Student(const char* name, int age, int stunum) : Human(name, age)  // 콜론 초기화
	{
		this->stunum = stunum;
	}

	void study()
	{
		cout << "학번 : " << this->stunum << endl;
		cout << "22는 4, 23은 6, 24 8" << endl;
	}
};

int main()
{
	Human kim("김길동", 29);
	kim.intro();

	Student hong("홍길동", 15, 123456);  // 기존의 클래스를 재사용하면서 123456만 처리하는 클래스를 새로 만듬 -> 재사용성, 상속
	hong.intro();   // Human으로부터 상속받아서 intro를 사용할 수 있음.
	hong.study();
}

private는 자식에게조차도 숨겨야할 내부적인 정보를 저장할때 사용하면 된다.

부모 클래스만 단독으로 사용하며 자식은 이 멤버의 존재를 몰라도 상관없을때 private로 지정한다. 

반면 외부에 대해서는 숨기되 자식 클래스와 같이 사용해야할 멤버는 protected로 지정하여 정보를 공유한다.

 

상속 액세스 지정

클래스 선언문의 기반 클래스 이름앞에는 상속 액세스 지정자가 온다. 

이 지정자는 상속되는 멤버의 액세스 지정자를 자식 클래스에서 어떻게 변경할 것인지 지정한다.

키워드는 액세스 지정자와 같지만 의미는 완전히 다르다. 상속 액세스 지정자에 따라 상속 후 액세스 권한이 달라진다.

 

부모 클래스의 private 멤버는 상속만 될뿐 어떤 경우라도 자식클래스가 읽을 수 없다.

public, protected 으로 선언된 멤버변수는 상속 액세스 지정자에따라 상속 후의 속성이 변경된다.

 

상속 액세스 지정자가 public이면 기반 클래스의 속성이 그대로 유지된다.

이를 public 상속이라고하며 가장 일반적이라고 볼 수 있다. 상속 액세스 지정자가 private이거나 protected이면 상속 후 모든 멤버가 private나 protected로 변경된다. 이렇게되면 이후의 파생 클래스는 더 이상 멤버를 외부로 공개할 수 없다.

 

클래스는 가급적 멤버를 숨기려는 경향이 있어 상속 액세스 지정자의 디폴트는 private이다.

반면 구조체는 public 상속이 디폴트이다. 일반적으로 상속이라하면 부모의 액세스 속성이 그대로 유지되는 public 상속을 의미하며 private, protected 상속은 특수한 기법으로 사용된다.

 

 

멤버 함수 재정의

다음 멤버들은 고유한 처리를 담당하기때문에 상속의 대상에서 제외된다.

 

- 생성자과 소멸자

- 대입 연산자

- 정적 멤버

- 프렌드 관계지정

 

특정 클래스에 완전 종속적이며 해당 클래스만의 동작이나 지정이기때문에 굳이 물려줄 필요가 없는 것이다.

생성자는 초기화리스트에서 호출할 뿐 일단 생성이 완료되면 더 호출할 필요가 없고 대입 연산자도 같은 타입끼리만 복사하므로 파생 클래스가 소유해야할 이유가 없다.

 

다음예제는 Human 클래스에 intro 함수가 있는데 Student 클래스에서 intro 함수를 재정의한 것이다.

// 파일이름 : Override.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Human
{
protected:
	char name[12];
	int age;

public:
	Human(const char* name, int age)
	{
		strcpy(this->name, name);
		this->age = age;
	}

	void intro()
	{
		cout << "이름 : " << this->name << endl;
		cout << "나이 : " << this->age << endl;
	}
};


class Student : public Human
{
protected:
	int stunum;

public:
	Student(const char* name, int age, int stunum) : Human(name, age)
	{
		this->stunum = stunum;
	}

	void study()
	{
		printf("이이는 사, 이삼은 육, 이사는 팔\n");
	}

	void intro()  // 오버라이딩(함수 재정의)
		          // 자식 클래스만의 intro() 함수
	{
		printf("%d학번 %s입니다.\n", this->stunum, this->name);
	}
};

int main()
{
	Human hong("홍길동", 30);
	hong.intro();
	Student kim("김길동", 20, 123456);
	kim.intro();

	// 자식 클래스인데 이름, 나이만 출력하는 intro()를 출력하고싶다
	// => 자식 클래스 객체인데 부모 클래스의 intro가 필요하다.
	kim.Human::intro();   // => 함수의 소속 클래스를 써주면 가능
}

이 예제는 Human의 intro 함수를 Student에서 재정의한 것이다.

사람은 이름과 나이로 자신을 소개하지만, 학생은 나이보다 학번이 더 중요하다.

사람이 자신을 소개하는 방식과 학생이 자신을 소개하는 방식이 다르므로 intro 함수를 재정의하는 예제이다.

 

Human 클래스에 intro 함수가 있지만 이름과 나이를 출력하는 형식이 학생에게 어울리지않는다.

이럴때는 Student에 똑같은 원형으로 intro 함수를 재정의하여 학번과 이름을 밝히도록 수정한다.

Human 객체 hong과

Student 객체 kim에 대해 각각 intro 함수를 호출했는데 객체에 따라 실제 호출되는 함수가 다른 것을 알 수 있다.

 

그런데 본인은 Student 객체인데 intro 함수에 학번이아니라 이름, 나이만 출력하고 싶을때가 있을 수 있다.

자식 클래스인데 이름, 나이만 출력하는 intro()를 출력하고싶다
=> 자식 클래스 객체인데 부모 클래스의 intro가 필요하다.

kim.Human::intro();   // 함수의 소속 클래스를 써주면 가능

그럴때는 intro() 함수 앞에 쓰고자하는 클래스를 적어줌으로써 intro의 소속을 써주면 가능하다.

Human::intro(); 는 Human 클래스에 정의된 intro 함수를 의미한다.

 

오버로딩과 오버라이딩 규칙 구별하기

오버로딩 : 매개변수의 개수와 타입이 달라야함 => 함수의 다른 정의

오버라이딩 : 상속관계일때 함수이름과 매개변수 출력이 다 똑같아야함 => 함수의 재정의

 

 

C++ 상속의 특성

상속의 횟수나 깊이에는 제약이 없다.

사본을 만들다고해서 원본이 훼손되는 것은 아니어서 하나의 부모 클래스로부터 특성이 조금씩 다른 자식 클래스를 얼마든지 파생시킬 수 있다.

 

클래스 관계에서 부모, 자식의 용어는 상대적인 개념이다. 상속 계층의 중간에 있는 클래스는 부모에 대해서는 자식이지만 자식에 대해서는 부모가 된다.

 

상속 관계도의 위쪽에 있는 클래스를 선조 또는 조상이라하고 아래쪽에 있는 클래스를 후손이라고하며 최상위의 부모를 루트라고 부른다.

 

상속 관계의 아래쪽으로 내려올수록 더 많은 속성과 동작이 정의된다. 위쪽의 선조 클래스는 멤버가 많지 않아 일반적이고 포괄적인 사물을 표현하는데 비해 아래로 내려올수록 후손 클래스의 멤버가 늘어 특수하고 구제적인 사물을 표현한다.

// 파일이름 : InheritGraduate.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

class Human
{
protected:
	char name[12];
	int age;

public:
	Human(const char* name, int age)
	{
		strcpy(this->name, name);
		this->age = age;
	}

	void intro()
	{
		printf("이름 : %s\n", this->name);
		printf("나이 : %d\n", this->age);
	}
};


class Student : public Human
{
protected:
	int stunum;

public:
	Student(const char* name, int age, int stunum) : Human(name, age)
	{
		this->stunum = stunum;
	}
	void study()
	{
		printf("이이는 사, 이삼은 육, 이사 팔 \n");
	}
};

class Graduate : public Student
{
protected:
	char thesis[32];

public:
	Graduate(const char* name, int age, int stunum, const char* thesis) : Student(name, age, stunum)
	{
		strcpy(this->thesis, thesis);
	}

	void research()
	{
		printf("%s을 연구하고 논문을 쓴다.\n", this->thesis);
	}
};

int main()
{
	Graduate hong("홍길동", 45, 900102, "게임방상권분석");
	hong.research();
}

 

 

다중상속

C++은 두 개의 기반 클래스로부터 새로운 클래스를 파생하는 다중 상속을 지원한다.

부모를 여러개로 가지는 다중 상속은 예상치않은 오류를 겪게 되기때문에 별로 쓰지않는것이 좋다.

(소속관계에 대해서도 헷갈리기때문)

C++, 객체 지향책에는 다중 상속은 권장하지는 않고 참고로 이런게 있구나. 정도만 알면 될듯하다.

// 파일이름 : MultilnHerit.cpp
#include <stdio.h>
#include <conio.h>

class Date
{
protected:
	int year, month, day;

public:
	Date(int y, int m, int d) { year = y; month = m; day = d; }
	void OutDate() { printf("%d/%d/%d", year, month, day); }
};


class Time
{
protected:
	int hour, min, sec;

public:
	Time(int h, int m, int s) { hour = h; min = m; sec = s; }
	void OutTime() { printf("%d:%d:%d", hour, min, sec); }
};


class DateTime : public Date, public Time
{
private:
	bool bEngMessage;
	int milisec;

public:
	DateTime(int y, int m, int d, int h, int min, int s, int ms, bool b = false) : Date(y, m, d), Time(h, min, s)
	{
		milisec = ms;
		bEngMessage = b;
	}

	void OutNow()
	{
		printf(bEngMessage ? "Now is" : "지금은 ");
		OutDate();
		_putch(' ');
		OutTime();
		printf(".%d", milisec);
		puts(bEngMessage ? "." : " 입니다");
	}
};

int main()
{
	DateTime now(2021, 6, 04, 9, 39, 58, 99);
	now.OutNow();
}

Date 클래스는 날짜와 관련된 속성과 함수를 정의하고

Time 클래스는 시간과 관련된 기능을 캡슐화한다.

한 시점을 정확히 표현하려면 날짜와 시간이 모두 피룡하다. 두 기능이 이미 만들어져있으니 새로 만들 필요 없이 두 클래스로부터 다중 상속 받으면 된다.

 

클래스 정의문에 콤마( , ) 로 구분하여 부모 클래스를 나열하되 각 부모의 액세스 지정자는 개별적으로 지정한다.

다음 문장은 Date와 Time 클래스를 public 상속받아 Now 클래스를 선언한다.

 

이 선언문에 의해 Now는 날짜, 시간과 관련된 속성을 모두 상속 받으며 필요한 멤버를 더 추가할 수도 있다.

메세지 출력 언어를 지정하는 bEngMessage와 1/1000초 단위까지 정밀한 시간을 저장하기위해 milisec 멤버를 추가했다. 그리고 시간과 날짜를 같이 출력하는 OutNow 멤버 함수를 정의하여 상속받은 OutDate, OutTime 함수를 차례대로 호출한다.

단일 상속과 마찬가지로 상속받은 멤버는 부모의 생성자가 초기화하는데 클래스 선언문에 나타난 순서대로 호출된다.

위 예제는 Date, Time 순으로 상속받으므로 Date의 생성자가 먼저 호출되고 Time의 생성자가 나중에 호출된다.

Now 클래스의 경우 초기화 순서는 별 의미 없지만 생성자 리스트에서 호출하는 순서가 아니라 선언문의 순서를 따른다는 점에 유의하자.

 

클래스 재활용

포함

상속은 부모의 속성과 동작은 물려받아서 시간과 노력을 절감할 수 있다. 이왕 만드는김에 적극적으로 재활용하여 생산성을 높여야 할 것이다. 그러나 상속만이 클래스를 재활용하는 유일한 기법은 아니며 다른 방법도 있다.

 

포함(containment)은 객체를 멤버로 선언하여 해당 클래스의 기능을 재활용하는 기법이다.

클래스의 멤버는 타입에 제한이 없어 기본형뿐만아니라 객체를 포함할 수 있다. 클래스끼리 중첩되는 형식인데 구조체가 다른 구조체를 멤버로 포함하는 것과 같다.

// 파일이름 : MemObject.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

class Date
{
protected:
	int year, month, day;
	
public:
	Date(int y, int m, int d) { this->year = y; this->month = m; this->day = d; }
	void OutDate() { printf("%d/%d/%d", this->year, this->month, this->day); }
};


class Product
{
private:
	char name[64];
	char company[32];
	Date validto;
	int price;

public:
	Product(const char* name, const char* company, int y, int m, int d, int price) : validto(y, m, d)
	{
		strcpy(this->name, name);
		strcpy(this->company, company);
		this->price = price;
	}

	void OutProduct()
	{
		printf("이름     : %s\n", this->name);
		printf("제조사   : %s\n", this->company);
		printf("유효기간 : ");
		validto.OutDate();
		puts("");
		printf("가격     : %d\n", this->price);
	}
};

int main()
{
	Product shrimp("새우깡", "농심", 2020, 8, 15, 900);
	shrimp.OutProduct();
}

제품 하나에 대한 정보를 표현하는 Product 클래스는 제품명, 제조사, 가격, 유통 기한 등을 멤버로 가진다.

유통 기한을 표현하기위해 year, month, day 멤버를 일일이 선언할 필요 없이 이 정보를 캡슐화해놓은 Date 클래스 객체 하나를 선언하는 것이 더 간편할 것이다.

그래서 유효기간은 Date 타입의 validto 멤버로 선언했다.

 

클래스가 다른 클래스의 객체를 포함하는 관계를 Has a 관계라고한다. 상속 관계를 표현하는 is a 관계와는 달리 소유 관계이다. 유효기간 표현을 위해 날짜를 소유(Product has a Date)하는 것이지 제품이 일종의 날짜(Product is a Date)는 아니다. 절대적인 법칙은 아니지만 두 클래스 관계가 Is a  일때 상속 기법을 사용하고 Has a 관계일때는 포함 기법을 사용하는 것이 정석이다.

 

 

private 상속

클래스는 포함 객체의 액세스 속성을 마음대로 결정한다. Product가 validto를 외부에 공개하려면 public으로 선언하고 내부적으로만 사용하려면 private로 선언하면 된다.

제품의 유효기간은 고유한 정보이므로 숨기는 것이 합당하며 그래서 validto를 private로 선언했다.

포함관계에서는 포함하는 클래스가 포함되는 객체의 공개 여부를 임의로 결정할 수 있다.

 

그렇다면 상속의 경우에도 자식 클래스가 상속받은 멤버의 액세스 지정을 변경할 수 있을지 알아보자.

상속을 받았으면 자신의 소유가 되므로 원하는대로 은폐할 수 있어야한다. 이것을 가능하게 해주는 방법이 바로 상속 액세스 지정자이다. 지금까지 예제는 모두 원래의 액세스 속성이 유지되는 public 상속이었다.

 

private 상속은 부모의 멤버를 상속받으면서 private로 바꿔버린다. 자식 클래스 자신은 이 멤버를 액세스할 수 있지만 외부나 이차 파생되는 클래스는 더 이상 참조할 수 없다. private 상속은 포함과 유사한 효과가 나타나며 has a 관계를 구현하는 또 다른 방법이다.

// 파일이름 : PrivateInherit.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

class Date
{
protected:
	int year, month, day;

public:
	Date() {}
	Date(int y, int m, int d) { this->year = y; this->month = m; this->day = d; }
	void OutDate() { printf("%d/%d/%d", year, month, day); }
};

class Product : private Date
{
private:
	char name[64];
	char company[32];
	int price;
public:
	Product(const char* name, const char* company, int y, int m, int d, int price) : Date(y, m, d)
	{
		strcpy(this->name, name);
		strcpy(this->company, company);
		this->price = price;
	}
	void OutProduct()
	{
		printf("이름     : %s\n", this->name);
		printf("제조사   : %s\n", this->company);
		printf("유효기간 : ");
		OutDate();
		puts("");
		printf("가격     : %d\n", this->price);
	}
};

int main()
{
	Product shrimp("새우깡", "농심", 2020, 8, 15, 900);
	shrimp.OutProduct();
}

예제 코드에 몇가지 변화가 생겼는데, Date로 부터 private 상속을 받으며 validto 포함 멤버는 제거한다.

초기화 리스트에서 Date의 생성자를 호출하여 상속받은 멤버를 초기화한다.

Product는 Date의 모든 멤버를 상속받아 제품의 유효기간을 표현하며 내부 구조는 다음과같다.

 

구조는 바뀌었지만 실행 결과는 같으며 두 방식 모두 Date 클래스를 재활용한다.

날짜 정보가 클래스에 직접 소속되며 날짜를 출력할 때는 상속받은 OutDate 함수를 바로 호출하면 된다.

Is a 관계와는 달리 상속받은 멤버를 가질 뿐 외부에서는 읽을 수 없고 OutDate 함수도 호출할 수 없다.

 

포함과 상속은 Has a 관계를 표현한다는 면에서 유사하지만 차이점도 많다.

가장 큰 차이점은 복수 개의 객체를 동시에 재활용할 수 있는가의 여부이다. Product에 유효 기간뿐만 아니라 제조일자를 포함시키고 싶다면 Date 타입의 객체를 다른 이름으로 하나 더 선언하면 된다.

 

 

인터페이스 상속

함수 자체도 숨길 수 있다.

우선 private 상속과 public 상속의 차이점을 알아야하는데,

private 상속은 인터페이스는 상속받지 않고 객체의 기능만 재사용할 뿐이어서 구현 상속이라고 한다.

public 상속은 구현뿐만아니라 인터페이스까지고 상속받아 자기 것으로 만든다.

 

private 상속의 예시로 위에서했던(private 단원의) 예제코드를 볼 수 있다.

Date 객체를 포함하는 Product 객체는 OutDate함수를 호출하여 날짜를 출력 할 수 있지만 자신이 이 함수를 가진 것은 아니어서 외부에서는 호출할 수 없다.

 

중첩 클래스

클래스는 주로 멤버 변수와 멤버 함수로 구서오디지만 타입도 포함할 수 없다.

클래스 내부적으로만 사용되고 외부에 알릴 필요가 없는 클래스가 있다면 선언문 안에 다른 클래스를 중첩하여 선언한다. 클래스안에 클래스가 있다고 보면 편하다.

// 중첩클래스
// 파일이름 : NestClass.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

class Product // 외부 클래스
{
private:
	char name[64];
	char company[32];
	int price;


	class Date // 내부 클래스(inner 클래스)
	{
	protected:
		int year, month, day;

	public:
		Date(int y, int m, int d) { this->year = y; this->month = m; this->day = d; }
		void OutDate() { printf("%d/%d/%d", year, month, day); }
	};


	Date validto;
public:
	Product(const char* name, const char* company, int y, int m, int d, int price) : validto(y, m, d)
	{
		strcpy(this->name, name);
		strcpy(this->company, company);
		this->price = price;
	}

	void OutProduct()
	{
		printf("이름     : %s\n", this->name);
		printf("제조사   : %s\n", this->company);
		printf("유효기간 : ");
		validto.OutDate();
		puts("");
		printf("가격     : %d\n", this->price);
	}
};

int main()
{
	Product shrimp("새우깡", "농심", 2020, 8, 15, 900);
	shrimp.OutProduct();

	// Date now(12, 34, 56);   // 에러
}

Date 클래스가 외부에서는 전혀 필요치 않고 Product 클래스 안에서만 사용된다면 Date 클래스의 선언문을 통째로 Product 안으로 이동시킨다.

Product 안에서 Date 타입의 멤버를 선언할 수 있고 함수의 인수로 사용할 수도 있다.

 

포함기법과 유사하되 객체뿐만아니라 클래스 선언문까지 포함된다는 면이 다르다.

클래스의 동작을 도와주는 도우미 클래스가 필요하다면 외부에 둘 필요없이 선언문을 중첩시킨다.

Product가 자체적으로 필요한 모든 것을 다 소유하여 재사용성이 증가하면서 외부의 영향을 덜 받는다.

 

이렇게되면 Date는 외부로 알려지지 않으며 Product 안에서만 사용되는 지역 클래스가 된다.

main에서는 Date 타입을 알 수 없어 Date 타입의 객체를 생성할 수 없다. 도우미 클래스를 외부로 공개하고 싶다면 public 영역에 선언한다. 중첩된 클래스를 사용할때는 외부 클래스명을 밝혀준다.

Product::Date now(12, 34, 56);

Product 클래스에 속한 Date 타입의 객체 now를 생성한다는 뜻으로,

Date가 다른 클래스에 소속되어 있으니 Date라는 이름만으로 위치를 정확히 알 수 없으며 앞에 Product:: 을 붙여 소속을 정확하게 밝혀야한다.

반응형