14장 상속

2023. 11. 29. 16:52프로그래밍 공부/OOP

14.1 상속 기본사항

상속(inheritance)은

파생 클래스(derived class)로 알려진 새로운 클래스가

기본 클래스(base class)라고 불리는 다른 클래스에서 생성되는 프로세스이다. 

파생 클래스는 기본 클래스가 가진 모든 멤버 변수와 모든 일반 멤버 함수를 자동으로 가지며

추가 멤버 함수와 추가 멤버 변수를 가질 수 있다.


파생 클래스

급여가 있는 직원과 시간당 직원에 대한 기록이 있는 기록 보관 프로그램을 설계한다고 가정해 보자. 

이러한 클래스를 그룹화하는 데에는 자연스러운 계층 구조가 있다.

이들은 모두 직원이라는 속성을 공유하는 사람들의 클래스이다.

시간당 임금을 받는 직원은 직원의 하위 집합 중 하나이다.

또 다른 하위 집합은 매달 또는 일주일에 고정 임금을 받는 직원으로 구성된다.

프로그램이 모든 직원의 집합에 해당하는 유형이 필요하지 않을 수도 있지만,

직원에 대한 보다 일반적인 개념의 관점에서 생각하는 것은 유용할 수 있다.

예를 들어, 모든 직원은 이름과 사회 보장 번호를 가지며,

이름과 사회 보장 번호를 설정하고 변경하는 멤버 함수는 급여가 있는 직원과 시간당 직원이 동일할 것이다.
C++ 내에서 월급쟁이든 시간당이든 모든 직원을 포함하는 Employee이라는 클래스를 정의한 다음

이 클래스를 사용하여 시간당 직원과 월급쟁이 직원의 클래스를 정의할 수 있다.
클래스 Employee에는 클래스 Employee의 데이터 영역을 조작하는 멤버 함수도 포함된다.

디스플레이 14.1 및 14.2에는 클래스 Employee에 대한 한 가지 가능한 정의가 표시된다.

 

디스플레이 14.1 기본 클래스 Employee 인터페이스

//This is the header file employee.h.
//This is the interface for the class Employee.
//This is primarily intended to be used as a base class to derive
//classes for different kinds of employees.
#ifndef EMPLOYEE_H
#define EMPLOYEE_H

#include <string>
using std::string;

namespace SavitchEmployees
{

	class Employee
	{
	public:
		Employee( );
		Employee( const string& theName, const string& theSsn);
		string getName( ) const;
		string getSsn( ) const;
		double getNetPay( ) const;
		void setName( const string& newName);
		void setSsn( const string& newSsn);
		void setNetPay( double newNetPay);
		void printCheck( ) const;
	private:
		string name;
		string ssn;
		double netPay;
	};
    
} //SavitchEmployees

#endif //EMPLOYEE_H

 

디스플레이 14.2 기본 클래스 Employee 구현

//This is the file employee.cpp.
//This is the implementation for the class Employee.
//The interface for the class Employee is in the header file employee.h.
#include <string>
#include <cstdlib>
#include <iostream>
#include "employee.h"
using std::string;
using std::cout;

namespace SavitchEmployees
{
	Employee::Employee( ) : name("No name yet"),
	                        ssn("No number yet"), netPay(0)
	{
		//deliberately empty
	}
    
	Employee::Employee( const string& theName, const string& theNumber)
	   : name(theName), ssn(theNumber), netPay(0)
	{
		//deliberately empty
	}
    
	string Employee::getName( ) const
	{
		return name;
	}
    
	string Employee::getSsn( ) const
	{
		return ssn;
	}

	double Employee::getNetPay( ) const
	{
		return netPay;
	}
    
	void Employee::setName( const string& newName)
	{
		name = newName;
	}
    
	void Employee::setSsn( const string& newSsn)
	{
		ssn = newSsn;
	}
    
	void Employee::setNetPay ( double newNetPay)
	{
		netPay = newNetPay;
	}
    
	void Employee::printCheck( ) const
	{
		cout << "\nERROR: printCheck FUNCTION CALLED FOR AN \n"
		     << "UNDIFFERENTIATED EMPLOYEE. Aborting the program.\n"
		     << "Check with the author of the program about this bug.\n";
		exit(1);
	}
    
} //SavitchEmployees

 

(미분화된) Employee 객체를 가질 수 있지만 Employee를 정의하는 이유는

다른 종류의 직원에 대한 파생 클래스를 정의할 수 있기 때문이다.

특히 파생 클래스에서 printCheck 함수의 정의가 항상 변경되어 여러 종류의 직원이 다른 종류의 검사를 가질 수 있다.

이는 (디스플레이 14.2) 클래스 Employee의 함수 printCheck의 정의에 반영된다.

그런 (미분화된) Employee에 대한 수표를 인쇄하는 것은 거의 의미가 없다.

우리는 이 직원에 대해 아는 바가 없다.

따라서 우리는 printCheck 함수를 클래스 Employee 구현하여

기본 클래스 Employee 객체에 대해 printCheck가 호출되면 오류 메시지와 함께 프로그램이 중지된다.

보시다시피 파생 클래스는

의미 있는 직원 검사를 생성하기 위해

printCheck 함수를 재정의하기에 충분한 정보를 가지고 있다.
Employee 클래스에서 파생된 클래스는

자동으로 Employee 클래스의 모든 멤버 변수(name, ssn, netPay)를 갖게 된다.

Employee 클래스에서 파생된 클래스는

printCheck , getName , setName 등과 같은 클래스 Employee의 모든 멤버 함수와

Display 14.1에 나열된 기타 멤버 함수를 갖게 된다.

이는 일반적으로 파생된 클래스가 멤버 변수 및 멤버 함수를 상속한다는 말로 표현된다.
Employee 클래스의 두 개의 파생 클래스의 클래스 정의가 있는 인터페이스 파일은 

디스플레이 14.3(HourlyEmployee)과 14.4(SalariedEmployee)에 나와 있다.

Employe 클래스와 두 개의 파생 클래스를 동일한 네임스페이스에 배치했다.

C++는 동일한 네임스페이스에 있을 필요는 없지만 관련 클래스이므로

동일한 네임스페이스에 배치하는 것이 타당하다.

먼저 디스플레이 14.3에 주어진 파생 클래스 HourlyEmployee에 대해 설명하겠다.

 

디스플레이 14.3 파생 클래스 HourlyEmployee 인터페이스

//This is the header file hourlyemployee.h.
//This is the interface for the class HourlyEmployee.
#ifndef HOURLYEMPLOYEE_H
#define HOURLYEMPLOYEE_H

#include <string>
#include "employee.h"

using std::string;

namespace SavitchEmployees
{

	class HourlyEmployee : public Employee
	{
	public:
		HourlyEmployee( );
		HourlyEmployee( const string& theName, const string& theSsn,
		                double theWageRate, double theHours);
		void setRate( double newWageRate);
		double getRate( ) const;
		void setHours( double hoursWorked);
		double getHours( ) const;
		void printCheck( );
	private:
		double wageRate;
		double hours;
	};
    
} //SavitchEmployees

#endif //HOURLYEMPLOYEE_H

void printCheck() -> 함수의 정의를 변경하려면 상속된 멤버 함수의 선언만 나열한다.

 

파생 클래스의 정의는 다른 클래스 정의처럼 시작되지만 

다음과 같이(디스플레이 14.3) 클래스 정의의 첫 번째 줄에

콜론, 예약된 public 단어 및 기본 클래스 이름을 추가한다:
class HourlyEmployee : public Employee
{
파생된 클래스(예: HourlyEmployee)는

기본 클래스(예: Employee)의 모든 멤버 변수 및 멤버 함수를 자동으로 수신하며

추가 멤버 변수 및 멤버 함수를 추가할 수 있다.
HourlyEmployee 클래스의 정의에는 멤버 변수 name, ssn 및 netPay가 언급되어 있지 않지만 

HourlyEmployee 클래스의 모든 개체에는 name, sn 및 netPay라는 이름의 멤버 변수가 있다.

멤버 변수 name, ssn, netPay는 Employee 클래스에서 상속된다.

HourlyEmployee 클래스는 wageRate 및 hours라는 두 개의 추가 멤버 변수를 선언한다.

따라서 HourlyEmployee 클래스의 모든 객체에는

name , ssn , netPay , wageRate , 및 hours라는 다섯 개의 멤버 변수가 있다.

파생 클래스의 정의(예: HourlyEmployee)에는 추가된 멤버 변수만 나열된다.

기본 클래스에서 정의된 멤버 변수는 언급되지 않다.

이 변수는 파생 클래스에 자동으로 제공된다.
클래스 Employee의 멤버 변수를 상속하는 것과 마찬가지로

HourlyEmployee 클래스도 클래스 Employee에서 모든 멤버 함수를 상속한다.

따라서 HourlyEmployee 클래스는 클래스 Employee에서

멤버 함수 getName , getSsn , getNetPay , setName , setSsn , setNetPay 및 printCheck를 상속한다.

 

디스플레이 14.4 파생 클래스 SalariedEmployee 인터페이스

//This is the header file salariedemployee.h.
//This is the interface for the class SalariedEmployee.
#ifndef SALARIEDEMPLOYEE_H
#define SALARIEDEMPLOYEE_H

#include <string>
#include "employee.h"

using std::string;

namespace SavitchEmployees
{

	class SalariedEmployee : public Employee
	{
	public:
		SalariedEmployee( );
		SalariedEmployee ( const string& theName, const string& theSsn,
		double theWeeklySalary);
		double getSalary( ) const;
		void setSalary( double newSalary);
		void printCheck( );
	private:
		double salary; //weekly
	};
    
} //SavitchEmployees

#endif //SALARIEDEMPLOYEE_H

 

상속된 멤버
파생 클래스(derived class)는 기본 클래스(base class)의 모든 멤버 변수와 모든 일반 멤버 함수를 자동으로 가진다.

(이 장의 뒷부분에서 설명하는 것처럼, 자동으로 상속되지 않는 멤버 함수가 있다.)

기본 클래스의 이러한 멤버는 상속된다(inherited)고 한다.

이러한 상속된 멤버 함수와 상속된 멤버 변수는

한 가지 예외를 제외하고는 파생 클래스의 정의에서 언급되지 않고 자동으로 파생 클래스의 멤버이다.

본문에서 설명한 대로 상속된 멤버 함수의 정의를 변경하려면 파생 클래스의 정의에서 상속된 멤버 함수를 언급한다.

 

파생 클래스는 상속된 멤버 변수와 멤버 함수 외에도 새로운 멤버 변수와 새로운 멤버 함수를 추가할 수 있다.

새로운 멤버 변수와 새로운 멤버 함수에 대한 선언문은 클래스 정의에 나열된다.

예시)

파생 클래스 HourlyEmployee는 두 멤버 변수 wageRate와 hours를 추가하고

새로운 멤버 함수 setRate, getRate, setHours, getHours를 추가한다.

이는 14.3 디스플레이에 나와 있다.

이러한 정의를 변경하려는 경우가 아니라면

상속된 멤버 함수에 대한 선언문을 제공하지 않는다는 점에 유의하라.

현재는 파생 클래스에 대한 생성자 정의의 세부 사항에 대해 걱정하지 말자.

다음 하위 섹션에서 생성자에 대해 논의하자.
Display 14.5의 HourlyEmployee 구현과 같이

파생 클래스에 대한 구현 파일에서 추가된 모든 멤버 함수의 정의를 제공한다.

다음으로 논의할 점인 파생 클래스에서 멤버 함수의 정의를 변경하지 않는 한 상속된 멤버 함수에 대한 정의를 제공하지 않는다.

 

부모 및 자식 클래스
파생 클래스를 논의할 때 가족 관계에서 파생된 용어를 사용하는 것이 일반적이다.

부모 클래스(parent class) = 기본 클래스

자식 클래스(child class) = 파생 클래스

이렇게 하면 상속 언어가 매우 원활해진다.

예를 들어, 자식 클래스는 부모 클래스로부터 멤버 변수와 멤버 함수를 상속받는다고 말할 수 있다.

이 비유는 종종 한 단계 더 나아가 진행된다.

조상 클래스(ancestor class): 다른 클래스의 부모(또는 다른 수의 "반복 부모")의 부모인 클래스

클래스 A가 클래스 B의 조상이라면, 클래스 B는 클래스 A의 후손(descendant)이라고 불린다.

 

재정의

상속된 멤버 함수의 정의를 재정의하기(redefining)

= 상속된 멤버 함수의 정의는 파생 클래스의 정의에서 기본 클래스와 다른 의미를 갖도록 변경할 수 있다.

예를 들어, 멤버 함수 printCheck()는 파생 클래스 HourlyEmployee의 정의에서 다시 정의된다.

멤버 함수의 정의를 다시 정의하려면,

파생 클래스에서 추가되는 멤버 함수와 마찬가지로 클래스 정의에서 나열하여 새로운 정의를 부여하기만 하면 된다.

이는 클래스 HourlyEmployee의 재정의된 함수 printCheck()에 의해 설명된다(디스플레이 14.3 및 14.5).

 

디스플레이 14.5 파생 클래스 HourlyEmployee를 위한 구현

//This is the file hourlyemployee.cpp.
//This is the implementation for the class HourlyEmployee.
//The interface for the class HourlyEmployee is in
//the header file hourlyemployee.h.
#include <string>
#include <iostream>
#include "hourlyemployee.h"
using std::string;
using std::cout;
using std::endl;

namespace SavitchEmployees
{
	HourlyEmployee::HourlyEmployee( ) : Employee( ), wageRate(0), hours(0)
	{
		//deliberately empty
	}
    
	HourlyEmployee::HourlyEmployee( const string& theName,
	      const string& theNumber, double theWageRate, double theHours)
	    : Employee(theName, theNumber), wageRate(theWageRate), hours(theHours)
	{
		//deliberately empty
	}
    
	void HourlyEmployee::setRate( double newWageRate)
	{
		wageRate = newWageRate;
	}
    
	double HourlyEmployee::getRate( ) const
	{
		return wageRate;
	}

	void HourlyEmployee::setHours( double hoursWorked)
	{
		hours = hoursWorked;
	}
    
	double HourlyEmployee::getHours( ) const
	{
		return hours;
	}
    
	void HourlyEmployee::printCheck( )
	{
		setNetPay(hours * wageRate);
		cout << "\n__________________________________________\n";
		cout << "Pay to the order of " << getName( ) << endl;
		cout << "The sum of " << getNetPay( ) << " Dollars\n";
		cout << "__________________________________________\n";
		cout << "Check Stub: NOT NEGOTIABLE\n";
		cout << "Employee Number: " << getSsn( ) << endl;
		cout << "Hourly Employee. \nHours worked: " << hours
		     << " Rate: " << wageRate << " Pay: " << getNetPay( ) << endl;
		cout << "__________________________________________\n";
	}
    
} //SavitchEmployees

netPay를 printCheck 함수의 일부로 설정하기로 한 것은 netPay가 사용되기 때문이다.

어쨌든 이것은 계산 문제이지 프로그래밍 문제가 아니다.

하지만 파생 클래스에서 다시 정의할 때 C++에서 printCheck 함수에 상수를 놓을 수 있다.

 

클래스 SalariedEmployee은 클래스 Employee의 파생 클래스의 또 다른 예이다.

클래스 SalariedEmployee의 인터페이스는 디스플레이 14.4에 제공되며, 구현은 디스플레이 14.6에 제공된다.

SalariedEmployee 유형으로 선언된 객체는

클래스 SalariedEmployee의 정의에 주어진 Employee의 모든 멤버 함수와 멤버 변수를 포함한다.
클래스 SalariedEmployee가 상속된 변수를 하나도 나열하지 않고 클래스 Employee의 한 가지 함수, 

즉 SalariedEmployee에서 정의가 변경될 함수 printCheck만 나열해도 그렇다.

그러나 클래스 SalariedEmployee는 멤버 변수 급여뿐만 아니라 name, ssn, netPay의 세 가지 멤버 변수를 가지고 있다.

클래스 SalariedEmployee가 이러한 멤버를 가지려면

클래스 Employee의 멤버 변수와 멤버 함수(예: name, setName)을 선언할 필요가 없다.

클래스 SalariedEmployee는 프로그래머가 아무것도 하지 않고도 이러한 상속된 멤버를 자동으로 얻는다.
클래스 Employee는 HourlyEmployee와 SalariedEmployee의 두 클래스에 공통되는 모든 코드를 가지고 있다.

이렇게 하면 동일한 코드를 두 번 작성하는 수고를 덜 수 있다.

HourlyEmployee 클래스에 한 번, SalariedEmployee 클래스에 한 번. 상속을 통해 Employee 클래스의 코드를 재사용할 수 있다.

 

디스플레이 14.6 파생 클래스 SalariedEmployee 구현

//This is the file salariedemployee.cpp
//This is the implementation for the class SalariedEmployee.
//The interface for the class SalariedEmployee is in
//the header file salariedemployee.h.
#include <iostream>
#include <string>
#include "salariedemployee.h"
using std::string;
using std::cout;
using std::endl;

namespace SavitchEmployees
{
	SalariedEmployee::SalariedEmployee( ) : Employee( ), salary(0)
	{
		//deliberately empty
	}
    
	SalariedEmployee::SalariedEmployee( const string& theName,
	               const string& theNumber, double theWeeklyPay)
	      : Employee(theName, theNumber), salary(theWeeklyPay)
	{
		//deliberately empty
	}
    
	double SalariedEmployee::getSalary( ) const
	{
		return salary;
	}
    
	void SalariedEmployee::setSalary( double newSalary)
	{
		salary = newSalary;
	}

	void SalariedEmployee::printCheck( )
	{
		setNetPay(salary);
		cout << "\n________________________________________________\n";
		cout << "Pay to the order of " << getName( ) << endl;
		cout << "The sum of " << getNetPay( ) << " Dollars\n";
		cout << "_________________________________________________\n";
		cout << "Check Stub NOT NEGOTIABLE \n";
		cout << "Employee Number: " << getSsn( ) << endl;
		cout << "Salaried Employee. Regular Pay: "
		     << salary << endl;
		cout << "_________________________________________________\n";
	}   
} //SavitchEmployees

 

파생 클래스의 생성자

기본 클래스의 생성자는 파생 클래스에서 상속되지 않지만

파생 클래스 생성자의 정의 내에서 기본 클래스의 생성자를 호출할 수 있다. 

파생 클래스의 생성자는 기본 클래스의 생성자를 특별한 방식으로 사용한다. 

기본 클래스의 생성자는 기본 클래스에서 상속된 모든 데이터를 초기화한다.

따라서 파생 클래스의 생성자는 기본 클래스의 생성자를 호출하는 것으로 시작된다.

기본 클래스 생성자를 호출하는 특별한 구문은

디스플레이 14.5에 제공된 클래스 HourlyEmployee에 대한 생성자 정의에 의해 설명된다.

텍스트 열에 맞게 줄 바꿈이 약간 변경된 다음에서

해당 디스플레이에서 가져온 클래스 HourlyEmployee에 대한 생성자 정의 중 하나를 재현했다:

HourlyEmployee::HourlyEmployee(const string&theName,
      const string& theNumber, double theWageRate,
      double theHours)
    : Employee(theName, theNumber),
      wageRate(theWageRate), hours(theHours)
{
    //deliberately empty
}

 

콜론 뒤의 부분은 생성자 HourlyEmployee::HourlyEmployee에 대한 생성자 정의의 초기화 섹션이다.

부분 Employee(theName, theNumber)는 기본 클래스 Employee에 대한 두 개의 인수로 구성된 생성자를 호출하는 것이다.

기본 클래스 생성자를 호출하는 구문은 멤버 변수를 설정하는 데 사용되는 구문과 유사하다.

엔트리 wageRate ( theWageRate )는 멤버 변수 wageRate의 값을 theWageRate로 설정한다.

엔트리 Employee(theName, theNumber)는 기본 클래스 생성자 Employee를 theName 및 theNumber 인수로 호출한다.

모든 작업이 초기화 섹션에서 수행되므로 생성자 정의의 본문은 비어 있다.

다음은 디스플레이 14.5에서 HourlyEmployee 클래스에 대한 다른 생성자를 재생한다:

 

HourlyEmployee::HourlyEmployee( ) : Employee( ), wageRate(0), hours(0)
{
    //deliberately empty
}

 

이 생성자 정의에서 기본 클래스 생성자의 기본(제로 인수) 버전은 상속된 멤버 변수를 초기화하기 위해 호출된다.

항상 파생된 클래스 생성자의 초기화 섹션에 기본 클래스 생성자 중 하나의 호출을 포함해야 한다.


파생 클래스에 대한 생성자 정의에 기본 클래스에 대한 생성자 호출이 포함되지 않으면

기본 클래스 생성자의 기본(제로 인수) 버전이 자동으로 호출된다.

따라서 Employee()가 생략된 클래스에 대한 기본 생성자의 다음 정의는 방금 논의한 버전과 동일하다:
HourlyEmployee::HourlyEmployee( ) : wageRate(0), hours(0)
{
    //deliberately empty
}
그러나 자동으로 호출되는 경우에도 기본 클래스 생성자에 대한 호출을 항상 명시적으로 포함하는 것을 선호한다.
파생 클래스 객체에는 기본 클래스의 모든 멤버 변수가 있다.

파생 클래스 생성자가 호출되면 이러한 멤버 변수에 메모리가 할당되어야 하며 초기화되어야 한다.

상속된 멤버 변수에 대한 이러한 메모리 할당은 기본 클래스에 대한 생성자가 수행해야 하며

기본 클래스 생성자가 이러한 상속된 멤버 변수를 초기화하는 데 가장 편리한다.

따라서 파생 클래스에 대한 생성자를 정의할 때

기본 클래스 생성자 중 하나에 대한 호출을 항상 포함해야 한다.

(파생 클래스 생성자 정의의 초기화 섹션에)

기본 클래스 생성자에 대한 호출을 포함하지 않으면

기본 클래스의 기본(제로 인수) 생성자가 자동으로 호출된다.

(기본 클래스에 대한 기본 생성자가 없으면 오류가 발생한다.)

 

생성자 호출의 순서

기본 클래스 생성자에 대한 호출은 도출된 클래스 생성자에 의해 수행되는 첫 번째 동작이다.

따라서 클래스 B가 클래스 A에서 파생되고 클래스 C가 클래스 B에서 파생된 경우

클래스 C의 객체가 생성되면

1. 클래스 A에 대한 생성자를 호출하고

2. 클래스 B에 대한 생성자를 호출한 후 

3. 마지막으로 클래스 C 생성자의 나머지 동작을 수행한다.

 

 

파생 클래스의 개체에 두 가지 이상의 유형이 있다
일상적인 경험에서 HourlyEmployee는 직원이다.

C++에서도 동일한 종류의 작업이 유지된다.

HourlyEmployee는 클래스 Employee의 파생 클래스이므로,

HourlyEmployee 클래스의 모든 개체는 클래스 Employee의 개체를 사용할 수 있다.

특히, 함수가 유형 Employee의 인수를 필요로 할 때 HourlyEmployee의 인수를 사용할 수 있다.

HourlyEmployee 클래스의 개체를 유형 Employee 변수에 할당할 수 있다.

(그러나 주의하십시오. 일반적인 기존 Employee 객체를 HourlyEmployee 변수에 할당할 수는 없다.

결국, Employee가 반드시 HourlyEmployee는 아니다.)

 

물론 동일한 설명이 기본 클래스와 파생 클래스에 적용된다.

파생 클래스의 개체를 기본 클래스의 개체가 허용되는 모든 위치에 사용할 수 있다.

보다 일반적으로 클래스 유형의 개체는 해당 상위 클래스의 개체를 사용할 수 있는 모든 위치에 사용할 수 있다.

클래스 자식(child)이 클래스 조상(ancestor)에서 파생되고 클래스 손주(grandchild)가 클래스 자식(child)에서 파생되면

1. 클래스 손주의 객체는 클래스 자식의 객체를 사용할 수 있으며

2. 클래스 손주의 객체도 클래스 조상의 개체를 사용할 수 있는 모든 위치에 사용할 수 있다.

 

파생 클래스의 생성자
파생 클래스는 해당 기본 클래스의 생성자를 상속하지 않다.

그러나 파생 클래스의 생성자를 정의할 때

기본 클래스의 생성자에 대한 호출을 포함할 수 있고 포함해야 한다

(생성자 정의의 초기화 섹션 내에서).
기본 클래스의 생성자에 대한 호출을 포함하지 않으면 

파생 클래스 생성자가 호출될 때 기본 클래스의 기본(제로 인수) 생성자가 자동으로 호출된다.

 

 

함정: 기본 클래스의 private 멤버 변수 사용

HourlyEmployee 클래스의 객체(디스플레이 14.3 및 14.5)는

Employee 클래스(디스플레이 14.1 및 14.2)에서 name이라는 멤버 변수를 상속한다.

예를 들어 다음은 객체 joe의 멤버 변수 name 값을 "Josephine"으로 설정한다

(또한 멤버 변수 ssn을 "123-45-6789"로 설정하고

wageRate 및 hours를 모두 0으로 설정한다):
HourlyEmployee joe("Josephine", "123-45-6789", 0, 0);
joe.name 을 "Mighty-Joe"로 변경하려면 다음과 같이 변경할 수 있다:
joe.setName("Mighty-Joe");


name과 같은 상속된 멤버 변수를 어떻게 조작하는지는 좀 주의해야 한다.

HourlyEmployee 클래스의 멤버 변수 name이 Employe 클래스에서 상속되었지만

멤버 변수 name은 Employe 클래스의 정의에 있는 private 멤버 변수이다.

즉, name은 Employee 클래스의 멤버 함수 정의 내에서만 직접 접근할 수 있다.

기본 클래스에서 private 멤버 변수(또는 멤버 함수)는

파생 클래스의 멤버 함수 정의에서도 접근할 수 없으며

다른 클래스의 멤버 함수 정의에서도 접근할 수 없다.

따라서 클래스 HourlyEmployee에 name이 있는 멤버 변수가 있지만

(기본 클래스 Employee에서 상속됨)

HourlyEmployee 클래스 정의에 있는 모든 멤버 함수의 정의에서

멤버 변수 이름에 직접 접근하는 것은 불법이다.

 

예를 들어, 다음은 멤버 함수인 HourlyEmployee::printCheck(디스플레이 14.5에서 가져옴)의 본문에서 가져온 첫 번째 몇 줄이다:
void HourlyEmployee::printCheck( )
{
    setNetPay(hours * wageRate);
    cout << "\n__________________________________________\n";
    cout << "Pay to the order of " << getName( ) << endl;
    cout << "The sum of " << getNetPay( ) << " Dollars\n";
netPay 멤버 변수 값을 설정하기 위해 멤버 함수 setNetPay를 사용해야 하는 이유가 궁금했을 수 있다.

 

멤버 함수 정의의 시작 부분을 다음과 같이 다시 작성하고 싶을 수 있다:
void HourlyEmployee::printCheck( )
{
    netPay = hours * wageRate; Illegal use of netPay.
코멘트에서 알 수 있듯이, 이것은 작동하지 않을 것이다.

멤버 변수 netPay는 Employee 클래스의 private 멤버 변수이며,

HourlyEmployee와 같은 파생 클래스가 netPay 변수를 상속하지만 직접 접근할 수는 없다.

일부 public 멤버 함수를 사용하여 멤버 변수 netPay에 접근해야 한다.

HourlyEmployee 클래스의 printCheck 정의를 수행하는 올바른 방법은 Display 14.5(이전에 일부가 표시됨)에서 수행한 방법이다.

 

기본 클래스에서 name과 netPay가 private로 상속되는 변수라는 사실도 

단순히 변수 이름과 netPay를 사용하는 대신에 

HourlyEmployee::printCheck의 정의에서 접근자 getName과 getNetPay를 사용해야 하는 이유를 설명해준다.

private 상속되는 멤버 변수를 이름으로 언급할 수는 없다.

대신 기본 클래스에서 정의된 접근자와 설정자(예: getName과 setName)를 사용해야 한다.

(접근자(accessor): 클래스의 멤버 변수에 접근할 수 있는 함수

설정자(mutator): 클래스의 멤버 변수를 변경할 수 있는 함수.)

 

파생 클래스의 멤버 함수의 정의에서

기본 클래스의 private 멤버 변수에 접근할 수 없다는 사실은 사람들에게 종종 잘못된 것처럼 보인다.
어쨌든, 만약 당신이 시간당 직원인데 이름을 바꾸고 싶다면, 

아무도 "미안해, name은 Employee 클래스의 private 멤버 변수야"라고 말하지 않는다. 

만약 당신이 시간당 직원이라면, 당신도 직원이다. 

자바에서는 이것 또한 사실이고,

HourlyEmployee 클래스의 개체는 Employee 클래스의 객체이기도 한다.

그러나 C++가 파생 클래스에서 private 멤버 변수와 private 멤버 함수에 접근할 수 있도록 허용했다면,

언제든지 당신이 파생 클래스를 만들어서 해당 클래스의 멤버 함수로 접근할 수 있다.

이것은 조금 더 노력하고 싶은 사람은 모든 private 멤버 변수에 접근할 수 있음을 의미한다.

이 시나리오는 문제를 보여주지만, 더 큰 문제는 의도적인 전복이 아니라 의도하지 않은 오류이다.

클래스의 private 멤버 변수가 파생 클래스의 멤버 함수 정의에서 접근할 수 있는 경우

멤버 변수가 실수로 또는 부적절한 방법으로 변경될 수 있다.
(접근자 및 설정자는 멤버 변수의 부적절한 변경을 방지할 수 있음을 기억하자.)

 

기본 클래스의 private 멤버 변수에 대한 이러한 제한을 피하기 위한

한 가지 가능한 방법에 대해서는 이 장 뒷부분에 있는 "protected 한정자"라는 항목에서 논의할 것이다.


함정: private 멤버 함수가 효율적으로 상속되지 않는다

앞의 함정 절에서 언급한 바와 같이,

기본 클래스에서 private 멤버 변수(또는 멤버 함수)는 기본 클래스의 인터페이스와 구현 밖에서는 직접 접근할 수 없으며,

심지어 파생 클래스에 대한 멤버 함수 정의에서도 접근할 수 없다.

private 멤버 함수는 직접적으로 이용할 수 없다는 점에서 private 변수와 같다.

그러나 멤버 함수의 경우에는 제한이 더 극적이다.

private 변수는 접근자나 설정자를 통해 간접적으로 접근할 수 있다.

private 멤버 함수는 단순히 사용할 수 없다.

마치 private 멤버 함수가 상속되지 않은 것과 같다.

이것이 문제가 되어서는 안 된다.

private 멤버 함수는 단지 도움 함수로 사용되어야 하며,

따라서 그것들이 정의된 클래스로 사용이 제한되어야 한다.

만약 여러분이 멤버 함수를 여러 상속된 클래스에서 도움 멤버 함수로 사용하기를 원한다면,

그것은 단순한 도움 함수가 아니며, 멤버 함수를 공개해야 한다.

 

protected 한정자

살펴본 바와 같이, 파생 클래스의 정의나 구현에서는 private 멤버 변수나 private 멤버 함수에 접근할 수 없다.

파생 클래스에서는 이름으로 접근할 수 있지만,

파생 클래스가 아닌 일부 클래스와 같이 다른 곳에서는 접근할 수 없는 멤버 변수와 함수의 분류가 있다.
클래스의 멤버 변수 또는 멤버 함수 앞에 private 또는 public이 아닌 protected 한정자를 사용하는 경우 

파생 클래스가 아닌 다른 클래스 또는 함수에 대해 멤버 변수에 private 라벨이 지정된 경우와 동일한 효과가 발생한다;
그러나 파생 클래스에서는 이름으로 변수에 접근할 수 있다.
예를 들어, 기본 클래스 Employee에서 파생된 HourlyEmployee 클래스를 생각해 보자.

우리는 HourlyEmployee::printCheck의 정의에서 상속된 멤버 변수를 조작하기 위해

접근자 및 설정자 멤버 함수를 사용해야 했다.

클래스 Employee의 모든 개인 멤버 변수에 private 대신 protected 키워드가 라벨로 지정된 경우,

파생 클래스 Employee::printCheck의 HourlyEmployee의 정의는 다음과 같이 단순화될 수 있다:

void HourlyEmployee::printCheck( )
//Only works if the member variables of Employee are marked

//protected instead of private.
{
    netPay = hours * wageRate;
    cout << "\n__________________________________________\n";
    cout << "Pay to the order of " << name << endl;
    cout << "The sum of " << netPay << " Dollars\n";
    cout << "____________________________________________\n";
    cout << "Check Stub: NOT NEGOTIABLE\n";
    cout << "Employee Number: " << ssn << endl;
    cout << "Hourly Employee. \nHours worked: " << hours
            << " Rate: " << wageRate << " Pay: " << netPay << endl;
    cout << "____________________________________________\n";
}

파생 클래스 HourlyEmployee에서 상속된 멤버 변수 name, netPay 및 ssn은

기본 클래스 Employee에서 protected으로 표시된 경우 이름으로 접근할 수 있다.

그러나 Employee 클래스에서 파생되지 않은 클래스에서는 이러한 멤버 변수가 private으로 표시된 것처럼 처리된다.
기본 클래스에서 protected로 표시된 멤버 변수는 파생 클래스에서도 protected로 표시된 것처럼 작용한다.

예를 들어, 클래스 HourlyEmployee의 파생 클래스 PartTimeHourlyEmployee를 정의했다고 가정해 보겠다.

클래스 PartTimeHourlyEmployee는

클래스 Employee에서 HourlyEmployee가 상속하는 멤버 변수를 포함하여

클래스 HourlyEmployee의 모든 멤버 변수를 상속한다.

따라서 클래스 PartTimeHourlyEmployee는 멤버 변수 netPay, name 및 ssn을 갖게 된다.

이러한 멤버 변수가 클래스 Employee에서 protected으로 표시된 경우

클래스 PartTimeHourlyEmployee의 함수 정의에서 이름으로 사용할 수 있다.

파생 클래스(및 파생 클래스의 파생 클래스 등)를 제외하고 protected 멤버 변수는 private으로 표시된 것과 동일하게 처리된다.

protected 멤버 변수가 사용되는 것을 볼 수 있고 잘 알고 있어야 하기 때문에 이러한 논의를 주로 포함한다.

전부는 아니지만 많은 프로그래밍 전문가들은

protected 멤버 변수를 사용하는 것이 클래스 구현을 숨기는 원칙을 손상시키기 때문에 나쁜 스타일이라고 말한다.

모든 멤버 변수는 private으로 표시되어야 한다고 말한다.

모든 멤버 변수가 private으로 표시된 경우

상속된 멤버 변수는 파생 클래스 함수 정의에서 이름으로 접근할 수 없다.

그러나 이는 말처럼 나쁘지 않다.

상속된 private 멤버 변수는 private 상속된 변수를 읽거나 변경하는 상속된 함수를 호출하여 간접적으로 접근할 수 있다.

protected 멤버를 사용해야 하는지 여부에 대해 전문가들마다 의견이 다르기 때문에 이를 활용할지 여부에 대해 직접 결정해야 한다.

 

protected 멤버
클래스의 멤버 변수 앞에 protected 한정자를 사용하면(private 또는 public이 아닌) 

파생 클래스가 아닌 다른 클래스 또는 함수에 대해 멤버 변수에 private 라벨이 지정된 경우와 효과가 같다.

그러나 파생 클래스의 멤버 함수 정의에서 이름으로 변수에 접근할 수 있다.

마찬가지로 클래스의 멤버 함수 앞에 protected 한정자를 사용하면

파생 클래스가 아닌 다른 클래스 또는 함수에 대해 멤버 변수에 private 라벨이 지정된 경우와 효과가 같다.

그러나 파생 클래스의 멤버 함수 정의에서 protected 함수를 사용할 수 있다.

protected 멤버는 파생 클래스에서 protected로 표시된 것처럼 파생 클래스에서 상속된다.

즉, 기본 클래스에서 멤버가 protected 것으로 표시되면

기본 클래스에서 직접 파생된 클래스에서만 액세스할 수 있는 것이 아니라

모든 하위 클래스의 정의에서 이름으로 액세스할 수 있다.

 

멤버 함수 재정의

파생 클래스 HourlyEmployee의 정의(Display 14.3)에서 

새로운 멤버 함수 setRate , getRate , setHours , 그리고 getHours에 대한 선언을 부여하였다.

또한 클래스 Employee에서 상속된 멤버 함수 중 하나에 대해서만 함수 선언을 부여하였다.

함수 선언이 부여되지 않은 상속된 멤버 함수(setName, setSsn 등)는 그대로 상속된다.
이들은 기본 클래스 Employee에서와 같이 HourlyEmployee 클래스에서 동일한 정의를 가진다.

HourlyEmployee와 같이 파생 클래스를 정의할 때,

파생 클래스에서 다른 정의를 갖도록 변경하려는 상속된 멤버 함수에 대한 함수 선언만 나열한다.

클래스 HourlyEmployee의 구현을 살펴보면(Display 14.5), 상속된 멤버 함수 printCheck을 재정의했다.

또한 클래스SalariedEmployee는 14.6과 같이 멤버 함수 printCheck에 새로운 정의를 부여한다.

또한 두 클래스는 서로 다른 정의를 제공한다.

printCheck 함수는 파생 클래스에서 재정의된다.
디스플레이 14.7은 파생 클래스 HourlyEmployee 및 SalariedEmployee의 사용을 설명하는 시연 프로그램을 제공한다.

 

상속된 함수 정의
파생 클래스는 기본 클래스에 속하는 모든 멤버 함수(및 멤버 변수)를 상속한다.

그러나 파생 클래스에서 상속된 멤버 함수에 대해 다른 구현이 필요한 경우

해당 함수는 파생 클래스에서 재정의될 수 있다.

멤버 함수를 재정의할 때 기본 클래스와 선언이 동일하더라도

해당 선언을 파생 클래스의 정의에 나열해야 한다.

기본 클래스에서 상속되는 멤버 함수를 재정의하지 않으려면

파생 클래스의 정의에 나열하지 말라.


재정의 대 오버로딩

파생 클래스에서 함수 정의를 재정의(redefining)하는 것을 함수 이름을 오버로딩(overloading)하는 것과 혼동하지 말자.

함수 정의를 재정의할 때 파생 클래스에 주어진 새로운 함수 정의는 매개변수의 수와 종류가 동일한다.

함수를 오버로딩할 때 파생 클래스의 함수는

기본 클래스의 함수와 다른 수의 매개변수를 갖거나 다른 종류의 매개변수를 가지며

파생 클래스는 두 함수를 모두 갖는다.

 

예를 들어, 다음과 같은 함수 선언이 있는 함수를 클래스 HourlyEmployee의 정의에 추가했다고 가정한다:
void setName(string firstName, string lastName);
클래스 HourlyEmployee는 인수 2개를 가진 함수 setName을 가지며 다음 인수 1개를 가진 함수 setName도 상속한다:
void setName(string newName);
클래스 HourlyEmployee에는 setName이라는 두 개의 함수가 있다.

이는 함수 이름 setName을(를) 오버로딩하는 것이다.
반면, 클래스 Employee와 클래스 HourlyEmployee는 모두 다음 함수 선언으로 함수를 정의한다:
void printCheck( );
이 경우 클래스 HourlyEmployee는 printCheck라는 하나의 함수만 있지만, 

클래스 HourlyEmployee에 대한 printCheck 함수의 정의가 클래스 Employee에 대한 정의와 다르다.

이 경우 printCheck 함수가 재정의되었다.
만약 여러분이 재정의와 오버로딩을 혼동한다면, 한 가지 위안이 있다: 그것들은 모두 합법적이다.

그러므로, 그것들을 구별하는 것을 배우는 것보다 그것들을 사용하는 방법을 배우는 것이 더 중요하다.

그럼에도 불구하고, 여러분은 그것들의 차이점을 배워야 한다.

 

디스플레이 14.7 파생 클래스 이용

#include <iostream>
#include "hourlyemployee.h"
#include "salariedemployee.h"
using std::cout;
using std::endl;
using SavitchEmployees::HourlyEmployee;
using SavitchEmployees::SalariedEmployee;

int main( )
{
	HourlyEmployee joe;
	joe.setName("Mighty Joe");
	joe.setSsn("123-45-6789");
	joe.setRate(20.50);
	joe.setHours(40);    
	cout << "Check for " << joe.getName( )
	     << " for " << joe.getHours( ) << " hours.\n";
	joe.printCheck( );
	cout << endl;
    
	SalariedEmployee boss("Mr. Big Shot", "987-65-4321", 10500.50);
	cout << "Check for " << boss.getName( ) << endl;
	boss.printCheck( );
    
	return 0;
}

함수 setName, setSsn, setRate, setHours 및 getName은 Employee 클래스에서 변경되지 않고 상속된다.

printCheck 함수가 재정의되었다.

getHours 함수가 파생된 HourlyEmployee 클래스에 추가되었다.

 

샘플 대화 상자

Check for Mighty Joe for 40 hours.
________________________________________________
Pay to the order of Mighty Joe
The sum of 820 Dollars
________________________________________________
Check Stub: NOT NEGOTIABLE
Employee Number: 123-45-6789
Hourly Employee.
Hours worked: 40 Rate: 20.5 Pay: 820
_________________________________________________
Check for Mr. Big Shot
__________________________________________________
Pay to the order of Mr. Big Shot
The sum of 10500.5 Dollars
_________________________________________________
Check Stub NOT NEGOTIABLE
Employee Number: 987-65-4321
Salaried Employee. Regular Pay: 10500.5
_________________________________________________

 

서명
함수의 서명(signature): const 키워드와 앰퍼샌드(ampersand, &)를 포함하지 않고 매개 변수 목록에 있는 일련의 유형을 가진 함수의 이름

함수 이름을 오버로딩할 때 이 서명의 정의를 사용하여

함수 이름의 두 정의가 서로 다른 서명을 가져야 한다.

(일부 전문가는 const 및/또는 &를 서명의 일부로 포함하지만

우리는 오버로딩을 설명하는 데 사용되는 정의를 원했다.)

파생 클래스에서 기본 클래스와 동일한 이름을 가지지만 다른 서명을 가진 함수는 재정의되지 않고 오버로딩된다.


재정의된 기본 함수에 대한 액세스

함수가 파생 클래스에서 기본 클래스의 정의와 다른 정의를 갖도록 정의했다고 가정해 보겠다.

기본 클래스에 주어진 정의가 파생 클래스 객체에게 완전히 손실되는 것은 아니다.

그러나 파생 클래스의 객체와 함께 기본 클래스에 주어진 함수의 버전을 호출하려면

"(내가 파생 클래스의 개체임에도 불구하고)

기본 클래스에 주어진 것과 같이 이 함수의 정의를 사용하십시오"

라고 말하는 방법이 필요하다.

이렇게 말하는 방법은 범위 지정 연산자(::)를 기본 클래스의 이름과 함께 사용하는 것이다.

예를 들어 자세한 내용을 명확히 해야 한다.


기본 클래스 Employee(Display 14.1)과 파생 클래스인 HourlyEmployee(Display 14.3)를 생각해 보자.

두 클래스 모두에 printCheck( ) 함수가 정의되어 있다.

이제 다음과 같이 각 클래스의 객체가 있다고 가정한다:
Employee JaneE;
HourlyEmployee SallyH;
그리고 나서
JaneE.printCheck( );
클래스 Employee에 제공되는 printCheck의 정의를 사용한다. 그리고
SallyH.printCheck( );
는 클래스 HourlyEmployee에 주어진 printCheck의 정의를 사용한다.


그러나 파생 클래스 객체 SallyH를 printCheck의 호출 객체로 사용하여 

기본 클래스 Employee의 정의에 주어진 printCheck 버전을 호출하려면 다음과 같이 수행한다:
SallyH.Employee::printCheck( );


물론 특정 클래스에서 제공하는 printCheck 버전을 사용할 가능성은 낮지만, 

다른 클래스와 다른 함수의 경우 클래스 객체가 파생된 기본 클래스의 함수 정의를 사용하는 경우도 있다.

예를 들어 Self-Test Practice 6을 참조하자.


상속되지 않는 함수

일반적으로 Derived가 파생 클래스이고 기본 클래스 Base를 가진 경우

Base 클래스의 모든 "일반" 함수는 Derived 클래스의 사용 가능한 상속된 멤버이다.
다만, 모든 실질적인 목적을 위하여 상속되는 것이 아닌 특수한 함수도 있다.

실질적인 문제로서 생성자와 private 멤버 함수는 상속되지 않는다는 점은 이미 살펴보았다.

소멸자(제14.2절에서 논함)도 사실상 상속되지 않는다.


복사 생성자는 상속되지 않지만 

파생 클래스(또는 해당 클래스의 경우)에 복사 생성자를 정의하지 않으면 

C++가 자동으로 복사 생성자를 생성한다.

그러나 이 기본 복사 생성자는 단순히 멤버 변수의 내용을 복사할 뿐
멤버 변수에 포인터나 동적 데이터가 있는 클래스에 대해 올바르게 동작하지 않는다.

따라서 클래스 멤버 변수가 포인터, 동적 배열 또는 다른 동적 데이터를 포함하는 경우

클래스에 대한 복사 생성자를 정의해야 한다.

이것은 클래스가 파생 클래스인지 여부에 관계없이 적용된다.


할당 연산자 =도 상속되지 않는다. 

기본 클래스 Base가 할당 연산자를 정의하지만 

파생 클래스 Derived가 정의하지 않은 경우 클래스 Derived는 할당 연산자를 갖지만 

C++가 생성하는 기본 할당 연산자이며(=를 정의하지 않은 경우)

Base에 정의된 기본 클래스 할당 연산자와는 아무런 관련이 없다.

할당 연산자를 정의하는 기술은

14.2절의 "파생 클래스에서의 할당 연산자와 복사 생성자" 항목에서 설명한다.


생성자, 소멸자, 할당 연산자가 상속되지 않는 것은 당연하다.

할당을 올바르게 수행하기 위해서는 기본 클래스가 가지고 있지 않은 정보가 필요하다.

즉, 파생 클래스에 도입된 새로운 멤버 변수에 대해 알아야 하다.


14.2 상속을 포함한 프로그래밍

이 절에서는 상속에 관한 좀 더 미묘한 부분들을 소개하고 상속과 관련된 프로그래밍 기법에 관한 또 다른 완전한 예를 제시한다.

이 절의 자료는 동적 배열을 사용하며(10장),

대부분의 주제는 동적 배열 또는 포인터와 기타 동적 데이터를 사용하는 클래스에만 관련이 있다.


파생 클래스의 할당 연산자와 복사 생성자

오버로딩된 할당 연산자와 생성자는 상속되지 않는다.

그러나 이들은 파생 클래스의 오버로딩된 할당 연산자와 복사 생성자의 정의에 사용될 수 있으며 거의 모든 경우에 사용되어야 한다.


파생 클래스에서 할당 연산자를 오버로딩시킬 때, 당신은 일반적으로 기본 클래스에서 오버로딩된 할당 연산자를 사용한다.

우리가 줄 코드 개요를 이해하는 것을 돕기 위해, 오버로딩된 할당 연산자는 클래스의 멤버 함수로 정의되어야 한다는 것을 기억하자.


Derived가 Base에서 파생된 클래스인 경우 

클래스 Derived에 대한 오버로딩된 할당 연산자의 정의는 일반적으로 다음과 같은 것으로 시작된다:
Derived& Derived:: operator =( const Derived& rightSide)
{
    Base:: operator =(rightSide);
정의 본문의 코드 첫 번째 줄은 클래스 Base의 할당 연산자에 대한 호출이다.

이렇게 하면 상속된 멤버 변수와 해당 데이터가 처리된다.
오버로딩된 할당 연산자의 정의는 다음으로 클래스 Derived의 정의에 도입된 새로운 멤버 변수를 설정한다.

이 기술을 포함하는 완전한 예는 이 장의 뒷부분에 있는 프로그래밍 예제 "부분적으로 채워진 배열과 백업"에 나와 있다.


유사한 상황에서 파생 클래스의 복사 생성자를 정의할 수 있다.

Derived가 Base에서 파생된 클래스이면

클래스 Derived의 복사 생성자 정의는

일반적으로 클래스 Base의 복사 생성자를 사용하여 상속된 멤버 변수와 데이터를 설정한다.

코드는 일반적으로 다음과 같은 것으로 시작된다:
Derived::Derived(const Derived& Object)
    : Base(Object), <probably more initializations>
{
기본 클래스 복사 생성자 Base(Object)의 호출은 

생성되는 Derived 클래스 객체의 상속된 멤버 변수를 설정한다.

Object는 Derived 유형이므로 Base 유형이기도 한다.

그러므로 Object는 클래스 Base에 대한 복사 생성자의 합법적인 인수이다.

기본 클래스에 복사 생성자가 포함된 완전한 예는

이 장의 뒷부분에 있는 프로그래밍 예제 "백업을 포함한 부분적으로 채워진 배열"에 나와 있다.


물론 이러한 기법은 기본 클래스에 대해

올바르게 작동하는 할당 연산자와 올바르게 작동하는 복사 생성자가 없는 한 작동하지 않는다.
즉, 기본 클래스 정의에는 복사 생성자가 포함되어야 하며 

자동으로 생성된 기본 할당 연산자가 기본 클래스에 대해 올바르게 작동하거나 

기본 클래스에 할당 연산자에 대한 오버로딩된 정의가 있어야 한다.

 

파생 클래스의 소멸자

기본 클래스에 올바르게 기능하는 소멸자가 있는 경우, 

기본 클래스에서 파생된 클래스에서 올바르게 기능하는 소멸자를 정의하는 것은 비교적 쉽다. 

파생 클래스에 대한 소멸자가 호출되면 기본 클래스의 소멸자를 자동으로 호출하므로 

기본 클래스 소멸자에 대한 명시적인 호출 작성이 필요 없으며, 항상 자동으로 발생한다.

따라서 파생 클래스 소멸자는

파생 클래스에 추가되는 멤버 변수(및 그들이 가리키는 모든 데이터)에 대한

delete 사용에 대한 걱정만 하면 된다.

상속된 멤버 변수에 대한 delete를 호출하는 것은 기본 클래스 소멸자의 작업이다.


클래스 B가 클래스 A에서 파생되고 클래스 C가 클래스 B에서 파생되면, 

클래스 C의 객체가 범위를 벗어나면,

1. 클래스 C에 대한 소멸자가 호출되고, 

2. 클래스 B에 대한 소멸자가 호출되고,

3. 클래스 A에 대한 소멸자가 호출된다.

소멸자 호출 순서는 생성자가 호출되는 순서의 반대임을 주목하자.

"부분적으로 채워진 배열"의 프로그래밍 예제에서 유도된 클래스에 소멸자를 작성하는 예를 보여준다

 

예: 백업을 포함한 부분적으로 채워진 배열

이 예제는 10장(디스플레이 10.10)에서 제시한 부분적으로 채운 배열 클래스 PFArrayD의 파생 클래스를 제시한다.

참고로 디스플레이 14.8에서 기본 클래스 PFArrayD에 대한 헤더 파일을 반복한다.

디스플레이 14.9에서 기본 클래스 PFArrayD의 구현에 대해 논의할 내용을 포함한다.

10장에서 제시된 정의에 중요한 한 가지 변경 사항이 있다.

멤버 변수를 private에서 protected로 변경했다.

이렇게 하면 파생 클래스의 멤버 함수가 이름으로 멤버 변수에 접근할 수 있다.


PFArrayD를 기본 클래스로 사용하여 파생 클래스 PFArrayDBak을 정의하겠다.

파생 클래스 PFArrayDBak의 객체는

기본 클래스 PFArrayD의 모든 멤버 함수를 가지며 클래스 PFArrayD의 객체처럼 사용할 수 있지만

클래스 PFArrayDBak의 객체는 다음과 같은 기능이 추가된다.

멤버 함수 backup: 부분적으로 채워진 배열의 모든 데이터를 백업 복사본으로 만들 수 있다.

나중에 프로그래머는 멤버 함수 backup을 사용하여 부분적으로 채워진 배열을 마지막 백업 호출 직전의 상태로 복원할 수 있다.

이러한 추가된 멤버 함수의 의미가 명확하지 않은 경우에는

디스플레이 14.12에 표시된 샘플 시연 프로그램을 미리 살펴보아야 한다.

파생 클래스 PFArrayDBak의 인터페이스는 디스플레이 14.10에 표시되어 있다.


클래스 PFArrayDBak는 부분적으로 채워진 배열의 백업 복사본을 보유하기 위해 두 개의 멤버 변수를 추가한다.

double* 유형의 멤버 변수 b: (상속되는) 작동하는 배열의 백업 버전이 있는 동적 배열을 가리킨다.

int 멤버 변수 B: 백업된 배열 b의 데이터가 얼마나 많이 채워졌는지 나타낸다.

PFArrayD(또는 PFArrayDBak)의 용량을 변경할 방법이 없으므로 용량 값을 백업할 필요가 없다.

부분적으로 채워진 배열을 처리하기 위한 모든 기본 함수는

기본 클래스 PFArrayD에서 변경되지 않고 상속된다.

이러한 상속된 함수는 기본 클래스 PFArrayD에서와 마찬가지로 상속된 배열 a와 상속된 int 변수를 조작한다.


클래스 PFArrayDBak에 대한 새로운 멤버 함수의 구현은 디스플레이 14.11에 나와 있다.

파생 클래스 PFArrayDBak의 생성자들은

기본 클래스의 생성자들에 의존하여 부분적으로 채워진 정규 배열(상속된 멤버 변수 a, used, capacity)을 설정한다.

또한 각 생성자는 a 배열과 같은 크기의 새로운 동적 배열을 만든다.

이 두 번째 배열은 a의 데이터를 백업하는 데 사용되는 배열 b이다.

 

멤버 함수 backup은 다음 코드를 사용하여 부분적으로 채워진 배열(및 사용)에서 백업 위치 b로 데이터를 복사한다:
usedB = used;
for ( int i = 0; i < usedB; i++)
    b[i] = a[i];
기본 클래스에 사용된 멤버 변수 a와 멤버 변수는 protected되며 private가 아님을 유의해야 한다.

그렇지 않으면 이전 코드는 상속된 멤버 변수 a와 used에 접근하므로 허용되지 않는다.
멤버 함수 restore: 단순히 b와 usedB에서 a와 used로 객체와 복사본을 뒤바꾼다.
파생 클래스 PFArrayDbak에 대한 오버로딩 할당 연산자의 정의는

기본 클래스 PFArrayD에 대한 할당 연산자의 호출로 시작된다.

이는 할당 연산자의 오른쪽에 있는 개체(매개변수 오른쪽에 있는 개체)에서 할당 연산자의 왼쪽에 있는 개체(호출 개체)로

멤버 변수 a, used 및 capacity의 모든 데이터를 복사한다.

기본 클래스에서 오버로딩 할당 연산자의 정의에서 이 작업이 제대로 수행되었다는 사실에 의존한다.

즉, 동일한 개체(상속된 부분에 대해)가 할당 연산자의 양쪽에서 발생할 때

기본 클래스 할당 연산자에 의존하여 올바르게 동작하고 복사 작업이 배열 a의 독립적인 복사본을 만든다고 가정한다.

파생 클래스 PFArrayDbak에 대한 오버로딩 할당 연산자 본문의 코드는 유사하게 독립적인 배열 b의 복사본을 만들어야 한다.
할당 연산자의 오른쪽과 왼쪽에 있는 개체의 용량이 다를 수 있으므로 새 배열 b를 만들어야 한다(대부분의 경우).

이 작업은 다음과 같이 수행된다:
if (oldCapacity != rightSide.capacity)
{
    delete [] b;
    b = new double[rightSide.capacity];
}
if문의 부울식은 할당 연산자의 양쪽에 있는 개체들이 동일한 용량을 가질 때 b가 삭제되지 않도록 보장한다.

특히 할당 연산자의 양쪽에 같은 개체가 나타날 때 배열 b가 삭제되지 않도록 보장한다.

그 이후에는 데이터의 복사가 일상적이다.
클래스 PFArrayDBak에 대한 소멸자는 상속된 멤버 변수 a에 대한 명시적인 언급이 없음을 유의하자.

다음 문장을 사용하여 재사용을 위해 배열 b의 메모리를 freestore로 다시 보낸다.

delete [] b;

파생 클래스 PFArrayDBak에 대한 소멸자에 a가 언급되지 않더라도 

상속된 배열 a에 대한 메모리도 freestore로 다시 전송된다.

파생 클래스 PFArrayDBak에 대한 소멸자가 호출되면

기본 클래스 PFArrayD에 대한 소멸자를 자동으로 호출하여 동작을 종료한다.

해당 소멸자는 다음 코드를 포함하여 다음과 같이 처리한다:

delete [] a;

우리반 PFArrayDBak 시연 프로그램은 디스플레이 14.12에 제공된다.

 

디스플레이 14.8 기본 클래스 PFArrayD 인터페이스

//This is the header file pfarrayd.h. This is the interface for the
//class PFArrayD. Objects of this type are partially filled arrays
//of doubles .
#ifndef PFARRAYD_H
#define PFARRAYD_H
class PFArrayD
{
public:
	PFArrayD( );
	//Initializes with a capacity of 50 .
    
	PFArrayD( int capacityValue);
    
	PFArrayD( const PFArrayD& pfaObject);
    
	void addElement( double element);
	//Precondition: The array is not full.
	//Postcondition: The element has been added.
    
	bool full( ) const;
	//Returns true if the array is full, false otherwise.
    
	int getCapacity( ) const;
    
	int getNumberUsed( ) const;    
	void emptyArray( );
	//Resets the number used to zero, effectively emptying the array.
    
	double& operator[](int index);
	//Read and change access to elements 0 through numberUsed - 1.
    
	PFArrayD& operator =( const PFArrayD& rightSide);
    
	 ̃PFArrayD( );
protected:
	double *a; //for an array of doubles.
	int capacity; //for the size of the array.
	int used; //for the number of array positions currently in use.
};

#endif //PFARRAYD_H

이 클래스는 멤버 변수를 private이 아닌 protected한 것을 제외하고는

디스플레이 10.10의 클래스와 동일하다.

이 클래스를 네임스페이스에 배치하는 것이 좋겠지만

예제를 단순하게 유지하기 위해 그렇게 하지 않았다.

 

디스플레이 14.9 기본 클래스 PFArrayD를 위한 구현

#include <iostream>
using std::cout;
#include "pfarrayd.h"

PFArrayD::PFArrayD( ) : capacity( 50), used( 0)
{
	a = new double[capacity];
}

PFArrayD::PFArrayD( int size) : capacity(size), used( 0)
{
	a = new double[capacity];
}

PFArrayD::PFArrayD( const PFArrayD& pfaObject)
  :capacity(pfaObject.getCapacity( )), used(pfaObject.getNumberUsed( ))
{
	a = new double[capacity];
	for (int i =0; i < used; i++)
		a[i] = pfaObject.a[i];
}
double& PFArrayD:: operator[](int index)
{
	if (index >= used)
	{
		cout << "Illegal index in PFArrayD.\n";
		exit(0);
	}
	return a[index];
}

PFArrayD& PFArrayD:: operator =( const PFArrayD& rightSide)
{
	if (capacity != rightSide.capacity)
	{
		delete [] a;
		a = new double[rightSide.capacity];
	}
    
	capacity = rightSide.capacity;
	used = rightSide.used;
	for ( int i = 0; i < used; i++)
		a[i] = rightSide.a[i];
	return *this;
}

PFArrayD::<PFArrayD( )
{
	delete [] a;
}

이것은 구현 파일 pfarrayd.cpp의 일부이다.

전체 구현은 디스플레이 10.11에 나와 있지만, 여기에 나와 있는 것이 이 장에 필요한 전부이다.

 

디스플레이 14.10 파생 클래스 PFArrayDBak 인터페이스

//This is the header file pfarraydbak.h. This is the interface for the
//class PFArrayDBak. Objects of this type are partially filled arrays
//of doubles.
//This version allows the programmer to make a backup copy and restore
//to the last saved copy of the partially filled array.
#ifndef PFARRAYDBAK_H
#define PFARRAYDBAK_H
#include "pfarrayd.h"

class PFArrayDBak : public PFArrayD
{
public:
	PFArrayDBak( );
	//Initializes with a capacity of 50.
    
	PFArrayDBak( int capacityValue);
    
	PFArrayDBak( const PFArrayDBak& Object);
    
	void backup( );
	//Makes a backup copy of the partially filled array.
    
	void restore( );
	//Restores the partially filled array to the last saved version.
	//If backup has never been invoked, this empties the partially
	//filled array.    
	PFArrayDBak& operator =( const PFArrayDBak& rightSide);
    
	~PFArrayDBak( );
private:
	double *b; //for a backup of main array.
	int usedB; //backup for inherited member variable used.
};

#endif //PFARRAYD H

 

디스플레이 14.11 파생 클래스 PFArrayDBak 구현

//This is the file pfarraydbak.cpp.
//This is the implementation of the class PFArrayDBak.
//The interface for the class PFArrayDBak is in the file pfarraydbak.h.
#include "pfarraydbak.h"
#include <iostream>
using std::cout;

PFArrayDBak::PFArrayDBak( ) : PFArrayD( ), usedB( 0)
{
	b = new double[capacity];
}
PFArrayDBak::PFArrayDBak(int capacityValue) :
                         PFArrayD(capacityValue), usedB(0)
{
	b = new double[capacity];
}

PFArrayDBak::PFArrayDBak( const PFArrayDBak& oldObject)
              : PFArrayD(oldObject), usedB(0)
{
	b = new double[capacity];
	usedB = oldObject.usedB;
	for ( int i = 0; i < usedB; i++)
		b[i] = oldObject.b[i];
}

void PFArrayDBak::backup( )
{
	usedB = used;
	for (int i = 0; i < usedB; i++)
		b[i] = a[i];
}

void PFArrayDBak::restore( )
{
	used = usedB;
	for ( int i = 0; i < used; i++)
		a[i] = b[i];
}

PFArrayDBak& PFArrayDBak:: operator =( const PFArrayDBak& rightSide)
{
	int oldCapacity = capacity;
	PFArrayD:: operator =(rightSide);
	if (oldCapacity != rightSide.capacity)
	{
		delete [] b;
		b = new double[rightSide.capacity];
	}
    
	usedB = rightSide.usedB;
	for ( int i = 0; i < usedB; i++)
		b[i] = rightSide.b[i];
        
	return * this;
}

PFArrayDBak::~PFArrayDBak( )
{
	delete [] b;
}

b는 배열 a의 복사본이다.
우리는 b = a;를 사용하고 싶지 않다.

 

PFArrayD:: operator =(rightSide);

기본 클래스 구성원 변수를 할당하려면 기본 클래스 할당 연산자에 대한 호출을 사용한다.

 

PFArrayDBak::~PFArrayDBak( )

기본 클래스 PFARrayD에 대한 destructor가 자동으로 호출되고 delete [ ] a;를 수행한다.

 

디스플레이 14.12 클래스 PFArrayDBak의 시연 프로그램

//Program to demonstrate the class PFArrayDBak.
#include <iostream>
#include "pfarraydbak.h"
using std::cin;
using std::cout;
using std::endl;

void testPFArrayDBak( );
//Conducts one test of the class PFArrayDBak.

int main( )
{
	cout << "This program tests the class PFArrayDBak.\n";
	char ans;
	do
	{
		testPFArrayDBak( );
		cout << "Test again? (y/n) ";
		cin >> ans;
	} while ((ans == 'y') || (ans == 'Y'));
    
	return 0;
}

void testPFArrayDBak( )
{
	int cap;
	cout << "Enter capacity of this super array: ";
	cin >> cap;
	PFArrayDBak a(cap);
    
	cout << "Enter up to " << cap << " nonnegative numbers.\n";
	cout << "Place a negative number at the end.\n";
    
	double next;
    
	cin >> next;
	while ((next >= 0) && (!a.full( )))
	{
		a.addElement(next);
		cin >> next;
	}    
	if (next >= 0)
	{
		cout << "Could not read all numbers.\n";
		//Clear the unread input:
		while (next >= 0)
			cin >> next;
	}    
	int count = a.getNumberUsed( );
	cout << "The following " << count
	     << " numbers read and stored:\n";         
	int index;
	for (index = 0; index < count; index++)
		cout << a[index] << " ";
	cout << endl;
    
	cout << "Backing up array.\n";
	a.backup( );
    
	cout << "Emptying array.\n";
	a.emptyArray( );
	cout << a.getNumberUsed( )
	     << " numbers are now stored in the array.\n";
         
	cout << "Restoring array.\n";
	a.restore( );
	count = a.getNumberUsed( );    
	cout << "The following " << count
	     << " numbers are now stored:\n";
	for (index = 0; index < count; index++)
		cout << a[index] << " ";
	cout << endl;
}

 

샘플 대화 상자

This program tests the class PFArrayDBak.
Enter capacity of this super array: 10
Enter up to 10 nonnegative numbers.
Place a negative number at the end.
1 2 3 4 5 -1
The following 5 numbers read and stored:
1 2 3 4 5
Backing up array.
Emptying array.
0 numbers are now stored in the array.
Restoring array.
The following 5 numbers are now stored:
1 2 3 4 5
Test again? (y/n) y
Enter capacity of this super array: 5
Enter up to 5 nonnegative numbers.
Place a negative number at the end.
1 2 3 4 5 6 7 -1
Could not read all numbers.
The following 5 numbers read and stored:
1 2 3 4 5
Backing up array.
Emptying array.
0 numbers are now stored in the array.
Restoring array.
The following 5 numbers are now stored:
1 2 3 4 5
Test again? (y/n) n

 

함정: 할당 연산자의 양쪽에서 동일한 객체

할당 연산자를 오버로딩시킬 때마다 항상 할당 연산자의 양쪽에 같은 객체가 생길 때 정의가 작동하는지 확인하라.

대부분의 경우에는 고유한 코드가 있는 특수한 경우로 만들어야 한다.

이러한 예는 프로그래밍 예제인 "백업을 포함한 부분적으로 채워진 배열"에서 앞에서 설명했다


예: PFArrayDBak의 대체 구현

파생 클래스 PFArrayDBak에 대한 멤버 함수의 정의를 부여하기 위해 

기본 클래스 PFArrayD의 멤버 변수를 보호해야 했던 것처럼 보일 수 있다.

결국 많은 멤버 함수가 상속된 멤버 변수 a, used 및 capacity을 조작한다.

디스플레이 14.11에서 제공한 구현은 실제로 a, used 및 capacity으로 나타내므로

이러한 특정 정의는 기본 클래스에서 protected 멤버 변수(private이 아닌)에 따라 달라진다.

그러나 기본 클래스에는 접근자 및 설정자가 충분하므로

조금만 더 생각하면 파생 클래스 PFArrayDBak의 구현을 다시 작성하여

기본 클래스 PFArrayD의 모든 멤버 변수가 protected되는 것이 아니라 private인 경우에도 작동할 수 있다.


디스플레이 14.13은 기본 클래스의 모든 멤버 변수가 protected되는 대신 

private인 경우에도 잘 작동하는 클래스 PFArrayDBak에 대한 대체 구현을 보여준다.

이전 구현과 다른 부분은 음영 처리되어 있다.

대부분의 변경 사항은 분명하지만 몇 가지 장점이 있다.


멤버 함수 backup을 고려해 보겠다.

이전 구현(14.11 표시)에서는 배열 항목을 a에서 b로 복사했다.

a는 이제 private한 것이므로 이름으로 접근할 수 없지만

배열 대괄호 연산자(operator[])를 오버로딩시켜 PFArrayD 유형의 객체에 적용하고 이 연산자는 PFArrayD에서 상속된다.

우리는 단순히 연산자[]를 호출 객체와 함께 사용한다.

순 효과는 a 배열에서 b 배열로 복사하는 것이지만 private 배열에 대해 이름을 언급한 적은 없다.

코드는 다음과 같다:

usedB = getNumberUsed( );
for ( int i = 0; i < usedB; i++)
    b[i] = operator[](i);

 

정의 중인 클래스의 연산자를 호출하는 구문을 반드시 참조하자.

superArray가 클래스 PFArrayDBak의 객체인 경우

superArray.backup( )에서 operator[] (i) 표기는 superArray[i]를 의미한다.


멤버 함수 restore에 대한 정의에서 operator[] (i) 표기를 사용할 수도 있었지만 

상속된 멤버 함수 emptyArray로 배열 a를 비운 다음 addElement를 사용하여 

백업된 요소를 추가하는 것과 마찬가지로 쉽다.

이러한 방식으로 프로세스에 사용된 private 멤버 변수도 설정한다.
이 대체 구현에서는 이전 구현과 동일하게 클래스 PFArrayDBak이 사용된다.

특히 디스플레이 14.12의 데모 프로그램은 두 구현 모두 동일하게 작동한다.

 

디스플레이 14.13 PFArrayDBak의 대체 구현

//This is the file pfarraydbak.cpp.
//This is the implementation of the class PFArrayDBak.
//The interface for the class PFArrayDBak is in the file pfarraydbak.h.
#include "pfarraydbak.h"
#include <iostream>
using std::cout;

PFArrayDBak::PFArrayDBak( ) : PFArrayD( ), usedB(0)
{
	b = new double[getCapacity( )];
}
PFArrayDBak::PFArrayDBak( int capacityValue) : PFArrayD(capacityValue), usedB(0)
{
	b = new double[getCapacity( )];
}
PFArrayDBak::PFArrayDBak( const PFArrayDBak& oldObject)
   : PFArrayD(oldObject), usedB(0)
{
	b = new double[getCapacity( )];
	usedB = oldObject.usedB;
	for ( int i = 0; i < usedB; i++)
		b[i] = oldObject.b[i];
}
void PFArrayDBak::backup( )
{
	usedB = getNumberUsed( );
	for ( int i = 0; i < usedB; i++)
		b[i] = operator[](i);
}

void PFArrayDBak::restore( )
{
	emptyArray( );
	for ( int i = 0; i < usedB; i++)
		addElement(b[i]);
}

PFArrayDBak& PFArrayDBak:: operator =( const PFArrayDBak& rightSide)
{
	int oldCapacity = getCapacity();
	PFArrayD:: operator =(rightSide);
	if (oldCapacity != rightSide.getCapacity( ))
	{
		delete [] b;
		b = new double[rightSide.getCapacity( )];
	}
    
	usedB = rightSide.usedB;
	for ( int i = 0; i < usedB; i++)
		b[i] = rightSide.b[i];
        
	return * this;
}

PFArrayDBak::~PFArrayDBak( )
{
	delete [] b;
}

이 구현은 기본 클래스의 모든 구성원 변수가 protected되지 않고 private인 경우에도 작동한다.

 

b[i] = operator[](i);

호출 객체와 함께 대괄호 연산자를 호출한다.


팁: 클래스는 클래스의 모든 객체의 private 멤버에 접근할 수 있다

디스플레이 14.13에 주어진 오버로딩 할당 작업자의 구현에서 다음과 같은 행을 고려한다:

usedB = rightSide.usedB;
for ( int i = 0; i < usedB; i++)
    b[i] = rightSide.b[i];

rightSide.usedB와 rightSide.b[i]는

사용된 B와 b가 호출 객체가 아닌 다른 객체의 private 멤버 변수이므로 이에 반대할 수 있다.

일반적으로 반대하는 것이 옳을 것이다.

그러나 객체 rightSide는 정의되는 클래스와 동일한 유형이므로 이는 허용 가능하다.


클래스의 정의에서 호출 개체의 개인 멤버뿐만 아니라 클래스의 모든 객체의 private 멤버에 접근할 수 있다.


팁: "Is a" 대 "Has a"

"is a" 관계

이 장 초반에 우리는 Employee라는 클래스를 기본 클래스로 사용하여 HourlyEmployee라는 파생 클래스를 정의했다.

이러한 경우 파생 클래스인 HourlyEmployee의 개체도 Employee 유형아다.

좀 더 간단히 말해서 HourlyEmployee는 Employee이다.

이것은 클래스 간의 "is a" 관계를 보여주는 한 가지 예이다.

 

"has a" 관계

그것은 더 단순한 클래스에서 더 복잡한 클래스를 만드는 한 가지 방법이다.
더 간단한 수업에서 더 복잡한 수업을 만드는 또 다른 방법은 "has a" 관계이라고 알려져 있다.

예를 들어, 날짜를 기록하는 클래스 Date가 있다면, 날짜를 추가할 수 있다 
직원 클래스에 Date 유형의 멤버 변수를 추가하여 Employee 클래스에 고용한다.

이 경우 직원은 "Date"를 가지고 있다고 말한다.

다른 예를 들어, 제트 엔진을 시뮬레이션할 적절한 이름의 클래스가 있고 우리는 다음과 같다 
여객기를 모의실험할 클래스를 정의하면, 우리는 PassengerAirPlane 클래스에 JetEngine 유형의 멤버 변수를 하나 이상 부여할 수 있다.

이 경우, 우리는 PassengerAirPlane "has a" JetEngine라고 말한다.


대부분의 상황에서, 여러분은 "is a"관계거나 "has a"관계라고 해서 코드가 작동하도록 만들 수 있다.

PassengerAirPlane 클래스를 JetEngine 클래스에서 파생된 클래스로 만드는 것은 우스꽝스러워 보이지만,

(그리고 그것은) 실행될 수 있고 (아마도 어렵게) 작동하도록 만들 수 있다.

다행히도, 가장 좋은 프로그래밍 기술은 단순히 영어에서 가장 자연스럽게 들리는 것을 따르는 것이다.

“A passenger airplane has a jet engine”고 말하는 것이

“A passenger airplane is a jet engine.”라고 말하는 것보다 더 이치에 맞다.

그러므로, JetEngine을 PassengerAirPlane 클래스의 멤버 변수로 두는 것이 더 프로그래밍이 타당하다.

PassengerAirPlane 클래스를 JetEngine 클래스에서 파생된 클래스로 만드는 것은 거의 이치에 맞지 않다.

 

protected 및 private 상속

지금까지 도출된 클래스에 대한 모든 정의는 다음과 같이 클래스 제목에 public이라는 키워드를 포함했다:
class SalariedEmployee : public Employee
{
이로 인해 공용이라는 단어가 보호(protected) 또는 사적(private)이라는 단어로 대체되어

다른 종류의 상속을 받을 수 있다고 의심하게 될 수 있다.

이 경우에는 귀하의 의심이 정확할 것이다.

그러나 protected 및 private 상속은 거의 사용되지 않는다.

완전성을 위해 이에 대한 간략한 설명을 포함한다.

protected 및 private 상속의 구문은 다음과 같이 설명된다:
class SalariedEmployee : protected Employee
{
상속을 위해 protected 키워드를 사용하면 기본 클래스에서 public 멤버가 상속될 때 파생 클래스에서 보호된다.

상속을 위해 private 키워드를 사용하면 기본 클래스의 모든 멤버(public, protected, private)가 파생 클래스에서 액세스할 수 없다.

즉, 기본 클래스에서 private로 표시된 것처럼 모든 멤버가 상속된다.
더욱이 protected 및 private 상속에서는 파생 클래스의 개체가 기본 클래스의 유형을 갖는 인수로 사용될 수 없다.

Derived가 protected 또는 private(public 대신)을 사용하여 Base에서 파생된 경우

Derived 유형의 객체는 Base 유형의 객체가 아니다.

"is a" 관계는 protected 및 private 상속과 연결되지 않다.

protected 및 private 상속에서 기본 클래스는 단순히 파생 클래스를 정의하는 데 사용되는 도구이다.

protected 및 private 상속이 어떤 목적을 위해 작동하도록 만들 수 있지만,

의심할 수 있듯이 거의 사용되지 않으며 다른 방법으로 사용할 수 있는 모든 용도가 달성될 수 있다.

protected 및 private 상속에 대한 자세한 내용은 디스플레이 14.14에 요약되어 있다.

 

디스플레이 14.14 Public, Protected, 그리고 Private 상속

기본 클래스에서
액세스 지정자
상속 유형
(파생 클래스 정의에서 클래스 이름 뒤에 지정)
  public protected private
public Public Protected Private
(멤버 함수 및 friend 정의에 이름만 사용 가능)
protected Protected Protected Private
(멤버 함수 및 friend 정의에 이름만 사용 가능)
private 파생 클래스에서 이름으로 액세스할 수 없다 파생 클래스에서 이름으로 액세스할 수 없다 파생 클래스에서 이름으로 액세스할 수 없다

protected 및 private 상속은 public 상속에 대해 설명한 의미에서 상속이 아니다.

protected 또는 private 상속의 경우 기본 클래스는 파생 클래스에서 사용할 도구일 뿐이다.


다중 상속

파생된 클래스는 둘 이상의 기본 클래스를 가질 수 있다. 구문은 매우 간단하다.

모든 기본 클래스는 쉼표로 구분하여 나열된다. 그러나 모호성에 대한 가능성은 많다.

만약 두 기본 클래스가 이름과 매개 변수 유형이 같은 함수를 가지고 있다면 어떻게 될까?

어떤 것이 상속될까? 만약 두 기본 클래스가 이름이 같은 멤버 변수를 가지고 있다면 어떻게 될까?

이 모든 질문에 답할 수 있지만, 이런 문제들과 다른 문제들은 다중 상속을 매우 위험한 일로 만든다.

어떤 전문가들은 다중 상속이 너무 위험하다고 생각하기 때문에 그것을 전혀 사용해서는 안 된다.

그것은 너무 극단적인 입장일 수도 있고 그렇지 않을 수도 있지만,

당신이 매우 경험이 많은 C++ 프로그래머가 될 때까지 다중 상속을 심각하게 시도해서는 안 되는 것은 사실이다.

그 시점에서, 당신은 덜 위험한 기술을 사용함으로써

거의 항상 다중 상속을 피할 수 있다는 것을 깨닫게 될 것이다.

우리는 이 책에서 다중 상속에 대해 논의하지 않을 것이지만, 더 발전된 참고 자료를 위해 남겨둘 것이다.

'프로그래밍 공부 > OOP' 카테고리의 다른 글

16장 탬플릿  (1) 2023.11.30
15장 다형성과 가상함수  (1) 2023.11.30
13장 재귀  (1) 2023.11.28
12장 스트림 및 파일 입출력  (1) 2023.11.27
11장 분할 컴파일 및 네임스페이스  (1) 2023.11.26