[C++] is a, has a, 업 캐스팅, 다운 캐스팅

·2023년 8월 23일
0

C++

목록 보기
12/17
post-custom-banner

is a와 has a

// is a
class Manager : public Employee // Manager is a Employee

// 클래스는 파생될수록 좀 더 구체화된다.
// 부모 클래스로 올라갈수록 일반화된다.
class BankAccount{};
class CheckingAccount : public BankAccount
class SavingAccount : public BankAccount

// 모든 관계는 is a로만 표현은 불가능하다.
// has a 
class Car
{
private:
	Engine e;
    Brake b;
};

업캐스팅

#include <iostream>
#include <string>

class Base 
{
  std::string s;

 public:
  Base() : s("기반") { std::cout << "기반 클래스" << std::endl; }

  void what() { std::cout << s << std::endl; }
};

class Derived : public Base 
{
  std::string s;

 public:
  Derived() : s("파생"), Base() { std::cout << "파생 클래스" << std::endl; }

  void what() { std::cout << s << std::endl; }
};

int main() 
{
  Base p;
  Derived c;

  std::cout << "=== 포인터 버전 ===" << std::endl;
  Base* p_c = &c; // 업캐스팅
  p_c->what();

  return 0;
}

실행 결과
기반 클래스
기반 클래스
파생 클래스
=== 포인터 버전 ===
기반

Derived 객체 c는 Base를 상속받은 객체이기 때문에 Base 객체를 가리키는 포인터가
c를 가리켜도 문제 없음 -> 업 캐스팅


다운캐스팅

#include <iostream>
#include <string>

class Base 
{
  std::string s;

 public:
  Base() : s("기반") { std::cout << "기반 클래스" << std::endl; }

  void what() { std::cout << s << std::endl; }
};

class Derived : public Base 
{
  std::string s;

 public:
  Derived() : s("파생"), Base() { std::cout << "파생 클래스" << std::endl; }

  void what() { std::cout << s << std::endl; }
};

int main() 
{
  Base p;
  Derived c;

  std::cout << "=== 포인터 버전 ===" << std::endl;
  Derived* p_p = &p;
  p_p->what();

  return 0;
}

컴파일 오류
error C2440: 'initializing' : cannot convert from 'Base *' to 'Derived *'

Derived*가 Base 객체를 가리키면 p_p->what() 호출 시 Derived의 what() 함수가
호출되어야 하는데 Base 객체는 Derived에 대한 정보가 없으므로 문제가 됨
-> 다운 캐스팅을 할 때는 주의가 필요


virtual 키워드

#include <iostream>

class Base {

 public:
  Base() { std::cout << "기반 클래스" << std::endl; }

  virtual void what() { std::cout << "기반 클래스의 what()" << std::endl; }
};
class Derived : public Base {

 public:
  Derived() : Base() { std::cout << "파생 클래스" << std::endl; }

  void what() { std::cout << "파생 클래스의 what()" << std::endl; }
};
int main() {
  Base p;
  Derived c;

  Base* p_c = &c;
  Base* p_p = &p;

  std::cout << " == 실제 객체는 Base == " << std::endl;
  p_p->what();

  std::cout << " == 실제 객체는 Derived == " << std::endl;
  p_c->what();

  return 0;
}

실행 결과
기반 클래스
기반 클래스
파생 클래스
== 실제 객체는 Base ==
기반 클래스의 what()
== 실제 객체는 Derived ==
파생 클래스의 what()

이전에는 Base*로 Derived 객체를 가리키는 경우 Base의 what()함수가 호출되었으나
virtual 키워드를 사용하면 Derived의 what() 함수가 호출됨
virtual 키워드가 붙은 함수를 가상 함수라고 부름

p_p->what();
"흠, p_c 는 Base 포인터니까 Base 의 what() 을 실행해야지"
"어 근데 what 이 virtual 이네?"
"잠깐. 이거 실제 Base 객체 맞어? 어 맞네."
"Base 의 what 을 실행하자"

p_c->what();
"흠, p_c 는 Base 포인터니까 Base 의 what() 을 실행해야지"
"어 근데 what 이 virtual 이네?"
"잠깐. 이거 실제 Base 객체 맞어? 아니네 Derived 객체네"
"그럼 Derived 의 what 을 실행해야지"

이렇게 컴파일 시에 어떤 함수가 실행될 지 정해지지 않고 런타임 시에 정해지는 일을
동적 바인딩이라고 부름


Employee 코드 수정 (다형성)

#include <iostream>
#include <string>

class Employee 
{
 protected:
  std::string name;
  int age;

  std::string position;  // 직책 (이름)
  int rank;              // 순위 (값이 클 수록 높은 순위)

 public:
  Employee(std::string name, int age, std::string position, int rank)
      : name(name), age(age), position(position), rank(rank) {}

  // 복사 생성자
  Employee(const Employee& employee) 
  {
    name = employee.name;
    age = employee.age;
    position = employee.position;
    rank = employee.rank;
  }

  // 디폴트 생성자
  Employee() {}

  virtual void print_info() 
  {
    std::cout << name << " (" << position << " , " << age << ") ==> "
              << calculate_pay() << "만원" << std::endl;
  }
  virtual int calculate_pay() { return 200 + rank * 50; }
};

class Manager : public Employee 
{
  int year_of_service;

 public:
  Manager(std::string name, int age, std::string position, int rank,
          int year_of_service)
      : year_of_service(year_of_service), Employee(name, age, position, rank) {}

  int calculate_pay() override { return 200 + rank * 50 + 5 * year_of_service; }
  void print_info() override 
  {
    std::cout << name << " (" << position << " , " << age << ", "
              << year_of_service << "년차) ==> " << calculate_pay() << "만원"
              << std::endl;
  }
};

class EmployeeList 
{
  int alloc_employee;        // 할당한 총 직원 수
  int current_employee;      // 현재 직원 수
  Employee** employee_list;  // 직원 데이터

 public:
  EmployeeList(int alloc_employee) : alloc_employee(alloc_employee)
  {
    employee_list = new Employee*[alloc_employee];
    current_employee = 0;
  }
  void add_employee(Employee* employee) 
  {
    // 사실 current_employee 보다 alloc_employee 가 더
    // 많아지는 경우 반드시 재할당을 해야 하지만, 여기서는
    // 최대한 단순하게 생각해서 alloc_employee 는
    // 언제나 current_employee 보다 크다고 생각한다.
    // (즉 할당된 크기는 현재 총 직원수 보다 많음)
    employee_list[current_employee] = employee;
    current_employee++;
  }
  int current_employee_num() { return current_employee; }

  void print_employee_info() 
  {
    int total_pay = 0;
    for (int i = 0; i < current_employee; i++) {
      employee_list[i]->print_info();
      total_pay += employee_list[i]->calculate_pay();
    }

    std::cout << "총 비용 : " << total_pay << "만원 " << std::endl;
  }
  
  ~EmployeeList() 
  {
    for (int i = 0; i < current_employee; i++) 
    {
      delete employee_list[i];
    }
    delete[] employee_list;
  }
};

int main() 
{
  EmployeeList emp_list(10);
  emp_list.add_employee(new Employee("노홍철", 34, "평사원", 1));
  emp_list.add_employee(new Employee("하하", 34, "평사원", 1));

  emp_list.add_employee(new Manager("유재석", 41, "부장", 7, 12));
  emp_list.add_employee(new Manager("정준하", 43, "과장", 4, 15));
  emp_list.add_employee(new Manager("박명수", 43, "차장", 5, 13));
  emp_list.add_employee(new Employee("정형돈", 36, "대리", 2));
  emp_list.add_employee(new Employee("길", 36, "인턴", -2));
  emp_list.print_employee_info();
  return 0;
}

실행 결과
노홍철 (평사원 , 34) ==> 250만원
하하 (평사원 , 34) ==> 250만원
유재석 (부장 , 41, 12년차) ==> 610만원
정준하 (과장 , 43, 15년차) ==> 475만원
박명수 (차장 , 43, 13년차) ==> 515만원
정형돈 (대리 , 36) ==> 300만원
길 (인턴 , 36) ==> 100만원
총 비용 : 2500만원

Employee*가 가리키고 있음에도 불구하고 Manager는 Manager 함수를
Employee는 Employee의 함수를 호출함
하나의 메소드를 호출했음에도 불구하고 여러가지 다른 작업들을 하는 것을 다형성이라고 부름

post-custom-banner

0개의 댓글