250x250
Notice
Recent Posts
Recent Comments
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 08-1 : 객체 포인터의 참조관계 본문

Programming Lang/C++

C++ Chapter 08-1 : 객체 포인터의 참조관계

시데브 2023. 7. 19. 15:23
728x90

객체 포인터 변수 : 객체의 주소 값을 저장하는 포인터 변수

 C++에서, AAA형 포인터 변수는 AAA객체 or AAA를 직접 혹은 간접적으로 상속하는 모든 객체를 가리킬 수 있다(객체의 주소 값을 저장할 수 있다.)

 

 다시 말해서 Person이라는 기초 클래스와 Student라는 유도 클래스, 추가적으로 Student를 상속하는 PartTimeStudent 클래스가 있다고 가정했을 때, 다음과 같은 문장들이 가능해진다.

Person * ptr = new Student();
Person * ptr = new PartTimeStudent(); // 간접 상속의 경우에도 가능
Student * ptr = new PartTimeStudent();

 

 이와 관련된 다음 예제를 살펴보자.

#include <iostream>
using namespace std;

class Person
{
public:
    void Sleep() { cout << "Sleep" << endl; }
};

class Student : public Person
{
public:
    void Study() { cout << "Study" << endl; }
};

class PartTimeStudent : public Student
{
public:
    void Work() { cout << "Work" << endl; }
};

int main(void)
{
    Person * ptr1 = new Student();
    Person * ptr2 = new PartTimeStudent();
    Student * ptr3 = new PartTimeStudent();
    ptr1->Sleep();
    ptr2->Sleep();
    ptr3->Study();
    delete ptr1; delete ptr2; delete ptr3;

    return 0;
}
Sleep
Sleep
Study

 위와 같은 방식이 가능한 이유는 IS-A 관계를 생각해보면 간단하다. Student의 객체는 Person 객체의 일종이다. 그러므로 Person형 객체 포인터 변수를 선언해도 논리적 오류가 없다. 마찬가지로, PartTimeStudent의 객체는 Student 객체의 일종이며, 동시에 Person 객체의 일종이다. 그렇기 때문에 유도 클래스 객체의 포인터 변수를 선언할 때 기초 클래스형 포인터 변수로 선언할 수 있는 것이다.

 

'오렌지미디어 급여관리 확장성 문제'의 1차적 해결과 함수 오버라이딩

 Chapter 7 처음 시작할 때 제시했던 문제를 생각해보자. 정규직만 운영하던 회사에서 영업직, 임시직이라는 고용 형태를 추가했다. 그런데 영업직은 정규직의 일종이라는 점을 생각했을 때, 둘은 IS-A 관계에 있다고 할 수 있다. 

 

 클래스 이름을 다음과 같이 정리했을 때,

  • 고용인 Employee
  • 정규직 PermanentWorker
  • 영업직 SalesWorker
  • 임시직 TemporaryWorker

상속 관계는 다음과 같이 구성할 수 있다. 

  • class PermanentWorker : public Employee
  • class SalesWorker : public PermanentWorker
  • class TemporaryWorker : public Employee

 그렇다면 이전에 다루었던 EmployeeManage1.cpp에 Employee 클래스를 도입하면, EmployeeHandler 클래스가 저장 및 관리할 대상이 Employee 객체 하나로 줄어들게 된다. 또한, 이후에 Employee 를 상속하는 클래스가 추가되더라도 EmployeeHandler 클래스를 변동 혹은 확장할 필요성이 사라지게 된다.

 

 위 문장처럼 이전 예제에 Employee 클래스를 도입한 버전이다.

#include <iostream>
#include <cstring>
using namespace std;

class Employee
{
private:
    char name[100];
public:
    Employee(char * name)
    {
        strcpy(this->name, name);
    }
    void ShowYourName() const
    {
        cout<<"name : "<<name<<endl;
    }
};

class PermanentWorker : public Employee
{
private:
    int salary; // 매달 지불해야 하는 급여액
public:
    PermanentWorker(char * name, int money)
        : Employee(name),salary(money)
    { }
    int GetPay() const
    {
        return salary;
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary : "<<GetPay()<<endl<<endl;
    }
};

class EmployeeHandler
{
private:
    Employee* empList[50];
    int empNum;
public:
    EmployeeHandler() : empNum(0)
    { }
    void AddEmployee(Employee* emp)
    {
        empList[empNum++]=emp;
    }
    void ShowAllSalaryInfo() const
    {
        /*
        for(int i=0; i<empNum; i++)
            empList[i]->ShowSalaryInfo();
        */
    }
    void ShowTotalSalaryInfo() const
    {
        int sum = 0;
        /*
        for(int i=0; i<empNum; i++)
            sum += empList[i]->GetPay();  
        */
        cout<<"salary sum : "<<sum<<endl;      
    }
    ~EmployeeHandler()
    {
        for(int i=0; i<empNum; i++)
            delete empList[i];
    }
};

int main(void)
{
    // 직원 관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
    EmployeeHandler handler;

    // 직원 등록
    handler.AddEmployee(new PermanentWorker("KIM", 1000));
    handler.AddEmployee(new PermanentWorker("LEE", 1500));
    handler.AddEmployee(new PermanentWorker("JUN", 2000));

    // 이번 달에 지불해야 할 급여의 정보
    handler.ShowAllSalaryInfo();

    // 이번 달에 지불해야 할 급여의 총합
    handler.ShowTotalSalaryInfo();

    return 0;
}

 위의 주석 처리된 부분은 잠시 넘어가고, 이번에는 다음 정보를 추가해보자.

  • 영업직 급여 : '기본급여(월 기본급여) + 인센티브'의 형태
  • 임시직 급여 :'시간당 급여 * 일한 시간'의 형태

 우선 '임시직'에 해당하는 클래스를 정의해보자.

class TemporaryWorker : public Employee
{
private:
    int workTime;       //이 달에 일한 시간의 합계
    int payPerHour;     //시간당 급여
public:
    TemporaryWorker(char * name, int pay)
        : Employee(name), workTime(0), payPerHour(pay)
    { }
    void AddWrokTime(int time)  //일한 시간의 추가
    {
        workTime += time;
    }
    int GetPay() const          //이 달의 급여
    {
        return workTime*payPerHour;
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary : "<<GetPay()<<endl<<endl;
    }
};

 이어서 '영업직'에 해당하는 클래스를 정의해보자.

class SalesWorker : public PermanentWorker
{
private:
    int salesResult;     //월 판매실적
    double bonusRatio;   //상여금 비율
public:
    SalesWorker(char * name, int money, double ratio)
        : PermanentWorker(name, money), salesResult(0), bonusRatio(ratio)
    { }
    void AddSalesResult(int value)
    {
        salesResult += value;
    }
    int GetPay() const
    {
        return PermanentWorker::GetPay()    //PermanentWorker의 GetPay 함수 호출
                    +(int)(salesResult*bonusRatio);
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary : "<<GetPay()<<endl<<endl;    //SalesWorker의 GetPay함수가 호출됨
    }
};

 위 클래스에는 PermanentWorker 클래스에도 이미 정의된 GetPay 함수와 ShowSalaryInfo 함수가 정의됐다. 이를 가리켜 함수 오버라이딩이라고 하는데, 이 경우에는 기초 클래스의 함수가 유도 클래스의 함수에 의해 가려지게 된다. 그래서 위 예제에서 GetPay 함수를 호출할 때 SalesWorker의 GetPay가 호출되는 것이다. 그렇기 때문에 SalesWorker 클래스 내부 혹은 객체에서 PermanentWorker의 GetPay함수를 호출하려면 다음과 같은 호출문을 작성해야 한다.

PermenentWorker::GetPay();

 객체에서 호출하려면 객체 이름 뒤에 .을 붙이고 작성하면 된다.

 

 그럼 이제 추가된 클래스들을 적용해 예제를 완성해보자.

#include <iostream>
#include <cstring>
using namespace std;

class Employee
{
private:
    char name[100];
public:
    Employee(char * name)
    {
        strcpy(this->name, name);
    }
    void ShowYourName() const
    {
        cout<<"name : "<<name<<endl;
    }
};

class TemporaryWorker : public Employee
{
private:
    int workTime;       //이 달에 일한 시간의 합계
    int payPerHour;     //시간당 급여
public:
    TemporaryWorker(char * name, int pay)
        : Employee(name), workTime(0), payPerHour(pay)
    { }
    void AddWorkTime(int time)  //일한 시간의 추가
    {
        workTime += time;
    }
    int GetPay() const          //이 달의 급여
    {
        return workTime*payPerHour;
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary : "<<GetPay()<<endl<<endl;
    }
};

class PermanentWorker : public Employee
{
private:
    int salary; // 매달 지불해야 하는 급여액
public:
    PermanentWorker(char * name, int money)
        : Employee(name),salary(money)
    { }
    int GetPay() const
    {
        return salary;
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary : "<<GetPay()<<endl<<endl;
    }
};

class SalesWorker : public PermanentWorker
{
private:
    int salesResult;     //월 판매실적
    double bonusRatio;   //상여금 비율
public:
    SalesWorker(char * name, int money, double ratio)
        : PermanentWorker(name, money), salesResult(0), bonusRatio(ratio)
    { }
    void AddSalesResult(int value)
    {
        salesResult += value;
    }
    int GetPay() const
    {
        return PermanentWorker::GetPay()    //PermanentWorker의 GetPay 함수 호출
                    +(int)(salesResult*bonusRatio);
    }
    void ShowSalaryInfo() const
    {
        ShowYourName();
        cout<<"salary : "<<GetPay()<<endl<<endl;    //SalesWorker의 GetPay함수가 호출됨
    }
};

class EmployeeHandler
{
private:
    Employee* empList[50];
    int empNum;
public:
    EmployeeHandler() : empNum(0)
    { }
    void AddEmployee(Employee* emp)
    {
        empList[empNum++]=emp;
    }
    void ShowAllSalaryInfo() const
    {
        /*
        for(int i=0; i<empNum; i++)
            empList[i]->ShowSalaryInfo();
        */
    }
    void ShowTotalSalaryInfo() const
    {
        int sum = 0;
        /*
        for(int i=0; i<empNum; i++)
            sum += empList[i]->GetPay();  
        */
        cout<<"salary sum : "<<sum<<endl;      
    }
    ~EmployeeHandler()
    {
        for(int i=0; i<empNum; i++)
            delete empList[i];
    }
};

int main(void)
{
    // 직원 관리를 목적으로 설계된 컨트롤 클래스의 객체 생성
    EmployeeHandler handler;

    // 정규직 등록
    handler.AddEmployee(new PermanentWorker("KIM", 1000));
    handler.AddEmployee(new PermanentWorker("LEE", 1500));

    // 임시직 등록
    TemporaryWorker * alba = new TemporaryWorker("Jung", 700);
    alba -> AddWorkTime(5); // 5시간 일한 결과 등록
    handler.AddEmployee(alba);

    // 영업직 등록
    SalesWorker * seller = new SalesWorker("Hong", 1000, 0.1);
    seller->AddSalesResult(7000); // 영업실적 7000
    handler.AddEmployee(seller);

    // 이번 달에 지불해야 할 급여의 정보
    handler.ShowAllSalaryInfo();

    // 이번 달에 지불해야 할 급여의 총합
    handler.ShowTotalSalaryInfo();

    return 0;
}

 그러나 여전히 주석은 해제하지 못했다. 이에 관련한 문제는 'Chapter 08-2 가상함수'에서 알아보고 다른 문제를 살펴보자.

 

 SalesWorker 클래스와 PermanentWorker 클래스의 내부를 살펴보면 ShowSalaryInfo 함수의 내용이 완전히 같다. 그럼에도 오버라이딩을 진행한 이유는 무엇일까? 그 이유는 ShowSalaryInfo 내부에 GetPay 함수가 포함되어있기 때문이다. 유도 클래스가 기초 클래스의 ShowSalaryInfo 함수를 그대로 상속받는다고 해도 그대로 해당 함수를 호출하면 기초 클래스의 GetPay 함수를 적용한 리턴값을 반환한다. 따라서 이 경우에는 함수의 내용이 완전히 같다고 해도 유도 클래스에 별도의 ShowSalaryInfo 함수를 정의해야 한다.

 


출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12

728x90