Home CPP Module 04 : 다형성, 추상 클래스
Post
Cancel

CPP Module 04 : 다형성, 추상 클래스

주제

  • 서브타입 다형성
  • 추상 클래스
  • 인터페이스

Ex00

주요 개념

다형성 (polymorphism)

  • 임시 다형성 (ad-hoc polymorphism)
    • overloading
  • 서브타입 다형성 (subtype polymorphism)
    • overriding
  • 매개변수화한 다형성 (parametric polymorphism)
    • 템플릿의 사용

가상 함수

  • 파생 클래스에서 재정의할 것으로 기대하고 정의한 클래스
  • 순수 가상함수
    • 기반 클래스에서는 별도의 정의를 하지 않는 클래스
    • 함수 선언 뒤에 = 0을 붙여서 선언
  • 동적 바인딩이 되어 런타임에 어떤 함수를 호출할 지가 결정된다.
    • vs 정적 바인딩
      • 컴파일 타임에 어떤 함수를 호출할 지를 알 수 있다.
      • 예시) 가상 함수 외의 함수들
    • 예시
      1. 다음의 두 클래스 B, C(B를 상속)가 있다고 하자.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        
         #include <iostream>
                    
         class B {
         public:
           virtual void bar();
           virtual void qux();
         };
                    
         void B::bar() {std::cout << "This is B's implementation of bar" << std::endl;}
                    
         void B::qux() {std::cout << "This is B's implementation of qux" << std::endl;}
        
        1
        2
        3
        4
        5
        6
        
         class C : public B {
         public:
           void bar() override;
         };
                    
         void C::bar() {std::cout << "This is C's implementation of bar" << std::endl;}
        
      2. 아래와 같이 C의 객체가 함수를 호출한다.

        1
        2
        
         B* b = new C();
         b->bar();
        
      3. b는 C::bar()를 호출한다.

    • 원리
      1. 가상 함수가 하나라도 있는 클래스에 대해 컴파일러는 가상 테이블을 만든다.
        • 가상 테이블(virtual table, vtable)

          Untitled

          • 함수 포인터의 배열
          • 클래스에서 접근 가능하도록 각각의 가상 함수에 대해 entry를 보유하고 있고 이들의 definition에 포인터를 저장한다.
      2. 함수가 호출되면 객체의 vpointer로 자신의 클래스의 vtable을 찾는다.
      3. vtable에서 해당 함수를 찾아 호출한다.

Q&A

  • 다음과 같이 호출할 때 복사 생성이 아닌, 디폴트 생성자가 호출되며, 아래에서 delete하게 되면 아래와 같은 에러 발생.

    1
    2
    
      const Animal *j = new Dog();
      const Animal *i(j);
    
    1
    
      a.out(45439,0x10d40ddc0) malloc: *** error for object 0x7f9df9c05870: pointer being freed was not allocated
    
    • 복사생성자는 포인터를 인자로 받지 않기 때문
  • 파생 클래스에서 복사 생성자를 정의할 때 에러 발생.

    1
    2
    3
    4
    5
    6
    
      Cat.cpp: In copy constructor ‘Cat::Cat(const Cat&)’:
      Cat.cpp:14:1: error: base class ‘class Animal’ should be explicitly initialized in the copy constructor [-Werror=extra]
          14 | Cat::Cat(const Cat& origin)
             | ^~~
      cc1plus: all warnings being treated as errors
      make: *** [Makefile:39: obj/Cat.o] Error 1
    
    1
    2
    3
    4
    5
    
      Cat::Cat(const Cat& origin)
      {
          _type = origin._type;
          std::cout << C_NAME << " Copy constructor called" << std::endl;
      }
    
    • 파생 클래스에서 복사 생성자를 정의할 때는 기반 클래스의 생성자를 명시적으로 호출해야한다. 그렇지 않으면 기반 클래스의 기본 생성자가 호출되어 변수의 초기화가 제대로 이루어지지 않을 수 있다.
      1
      2
      3
      4
      5
      6
      
        Cat::Cat(const Cat& origin)
        : Animal(origin)
        {
        _type = origin._type;
        std::cout << C_NAME << " Copy constructor called" << std::endl;
        }
      

      Ex01

주요 개념

얕은 복사 vs 깊은 복사

  • 얕은 복사 (shallow copy)
    • 원본 객체의 참조자로 새로운 객체를 생성하는 방식
    • 사본에 변동이 생기면 원본에도 반영된다.
  • 깊은 복사 (deep copy)
    • 원본과 완전하게 독립적인 사본을 생성하는 방식
    • 사본의 변경사항이 원본에 영향을 미치지 않는다.

오버로딩 vs 오버라이딩

두 개념 모두 다형성에 관련된 개념이며 클래스에서 동일한 이름의 함수를 여러개 만드는데 사용된다.

  • 오버로딩(overloading)
    • 동일한 함수명을 가졌지만 매개변수가 다른 함수를 여러 개 정의할 수 있는 기능.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
        class MyClass {
        public:
            int add(int a, int b) {
                return a + b;
            }
              
            double add(double a, double b) {
                return a + b;
            }
        };
      
  • 오버라이딩(overriding)
    • 파생 클래스가 기반 클래스의 가상 함수다시 정의하는 것.
      • 함수를 파생 클래스에 맞추어 구체적으로 구현하기 위한 목적으로 주로 사용됨.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
        class Base {
        public:
            virtual void display() {
                std::cout << "Base" << std::endl;
            }
        };
              
        class Derived : public Base {
        public:
            void display() {
                std::cout << "Derived" << std::endl;
            }
        };
      

과제 조건

  • Brain
    • string 배열
  • Dog, Cat
    • dog, cat이 모두 brain*를 가져야 함.
      • 생성될 때 brain을 동적할당
    • 복사 생성은 깊은 복사로 수행할 것

Q&A

  • 생성자 내부에서 변수를 초기화하는것에 비해 초기화 리스트가 같는 이점?
    • 변수를 생성자 내부에서 초기화하는 방식은 생성자 호출 후 할당하게 되어 비효율적이다.
  • void func(const int &index)와 같이 const, 참조자를 사용하는 이유
    • 참조자 사용 이유 : 함수를 호출할 때 메모리 상에 복사를 하지 않고 해당 변수의 주소를 통해 원본을 넘겨줄 수 있다. (메모리 측면에서 효율적)
    • const 사용 이유 : 원본을 넘겨주기 때문에 값의 변경이 이루어질 수 있기 때문에 const를 통해 원본의 변경을 막는다.
  • 복사 생성자, 대입 연산자에서 brain을 동적할당하는 여부가 다른 이유?
    • 복사 생성자

      1
      
        _brain = new Brain(*origin._brain);
      
    • 대입 연산자

      1
      
        *_brain = *(origin._brain);
      
    • 복사 생성자는 객체를 새로 생성하는 것이다.
      • 생성 + 초기화(변수의 값을 복사)
    • 반면 대입 연산자는 기존에 이미 생성된 객체에 멤버 변수를 복사해주는 것이다.
      • 변수의 값 복사만 이루어진다.

Ex02

주요 개념

추상 클래스 (Abstract class)

  • 특징
    • 하나 이상의 순수 가상 함수를 포함하는 클래스
    • 인스턴스를 생성할 수 없다.
      • 동작이 정의되지 않은 함수가 있기 때문.

      Untitled

  • 사용이 불가능한 경우
    • 변수 또는 멤버 변수
    • 함수의 전달되는 인수 타입
    • 함수의 반환 타입
    • 명시적 타입 변환의 타입

과제 조건

  • Animal 클래스를 추상 클래스로 만들기
    • 불완전한 Animal 클래스가 인스턴스화되는 것을 막기 위해
    • 방법 : 하나 이상의 순수 가상 함수를 포함시킨다.

Ex03

주요 개념

  • concrete class
    • 순수 가상 함수가 없는 클래스
    • 추상 클래스의 반대 개념
    • concrete : 실체가 있는
  • 인터페이스
    • 소멸자와 순수 가상함수만 선언된 클래스

구현 사항

  • 클래스
    • AMateria : Ice, Cure의 기반 클래스
      • Ice
      • Cure
    • ICharacter : Character의 인터페이스
      • Character
    • IMateriaSource : MateriaSource의 인터페이스
      • MateriaSource
  • 과제 설명
    • AMateria는 모든 무기를 아우르는 개념
      • 무기는 Ice(공격용), Cure(치료용)이 있다.
    • MateriaSource는 무기 상점 개념
      • 상점에는 최대 4개의 무기 전시대가 있다.
      • 무기 객체를 받아서 learn하면 무기 상점의 전시대 하나에 해당 무기를 전시한다.
      • ice, cure 중 하나를 create하면 전시대에 존재하는지 확인 후에 3D 프린터로 복사해서 준다.
    • Character는 게임 캐릭터 개념
      • 한 캐릭터가 최대 4개의 무기를 장착할 수 있다.
      • 상점에서 받아온 무기를 equip, unequip해서 장착/제거 할 수 있다.
      • use하여 공격 또는 치료를 할 수 있다.

Q&A

  • 인터페이스 ICharacter의 프로토타입을 AMateria 상단에 선언하는 이유?
    • 헤더를 include하는 대신 전방 선언하여 순환 참조(circular dependency)를 막는다
      • 전방 선언(forward declaration) : 식별자의 존재를 컴파일러에게 미리 알리는 것.
      • #ifndef 전처리 지시어
        • 헤더가 중복 include되는 것을 막아준다.
        • 순환 참조 문제는 해결이 안되기에 전방 선언이 필요하다.

기타

Call by value vs Call by reference

  • Call by value
    • 인자의 사본을 함수에 넘겨주는 방식
    • 함수 내에서 인자를 변경해도 밖에는 영향을 미치지 않는다.
  • Call by reference
    • 원본 인자를 전달하는 방식
    • 함수 내에서 인자를 변경하면 밖에도 영향을 미친다.
  • 결론
    • call by value : 인자를 복사해서 전달해야 하는 경우
    • call by reference : 인자의 참조자를 넘겨줘야 하는 경우
      • 이 방식이 인자를 복사하는데 필요한 오버헤드가 없어서 효율적이다.

Ref.

This post is licensed under CC BY 4.0 by the author.

CPP Module 03 : 상속

CPP Module 05 : 예외 처리