치춘짱베리굿나이스

[Rank 4] CPP 04 본문

42/42s Cursus

[Rank 4] CPP 04

치춘 2022. 9. 11. 22:36

CPP 04

Subtype polymorphism, abstract classes, interfaces

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

서브타입 다형성

포함 다형성이라고도 한다

상위 클래스의 메서드를 하위 클래스가 상속받은 뒤 다른 동작을 하게끔 재정의하여 사용하는 방법이며, 오버라이딩 (Overriding) 이라고도 한다

과제에서는 Animal 클래스를 상속받은 Dog, Cat 클래스가 각기 다른 소리를 내도록 오버라이딩하는 과정이 들어있다

가상 함수 (virtual function)

[C++] 가상함수(virtual) 사용법 총정리

코딩교육 티씨피스쿨

부모 클래스의 멤버 함수 중, 파생 클래스에서 재정의할 것을 기대하는 함수이다

기대 라는 단어가 뭔가 요상하기도 한데, 부모 클래스에서 이 멤버 함수가 자식 클래스에 의해 재정의될 것을 예상하고 있으니, 자식 클래스가 이 함수를 상속받아 재정의한다면 부모 클래스의 내용 대신 자식 클래스의 내용을 호출해라 라는 뜻이다

class FooClass {
    public:
        virtual void foo(void) {
            std::cout << "foo\n";
        }
};

class BarClass: public FooClass {
    public:
        void foo(void) {
            std::cout << "bar\n";
        }
};

가상 함수임을 나타내기 위해 함수의 머리에 virtual 키워드를 붙인다

언뜻 보면 굳이 안 붙여도 되지 않나? 싶은데, 가상함수를 사용하는 이유가 있다

일반적으로는 코드가 어떤 함수를 호출할 지 컴파일 단계에서 결정되며, 이를 ‘정적 바인딩’ 이라고 한다

class FooClass {
    public:
        void foo(void) {
            std::cout << "foo\n";
        }
};

class BarClass: public FooClass {
    public:
        void foo(void) {
            std::cout << "bar\n";
        }
};

// 정상적으로 작동

FooClass* foo = new FooClass();
BarClass* bar = new BarClass();

foo->foo(); // foo
bar->foo(); // bar

// 정적 바인딩으로 인한 문제 발생

FooClass* foo = new FooClass();
FooClass* bar = new BarClass(); // 타입이 부모 클래스의 포인터

foo->foo(); // foo
bar->foo(); // foo

virtual 키워드를 foo 함수에 붙이지 않았을 경우, 첫 번째 케이스에서는 bar 변수의 정적 타입이 BarClass* 이기 때문에 컴파일러도 BarClass의 함수를 호출하고, 예상대로 “bar”가 출력된다

하지만 두 번째 케이스에서는 bar의 생성자는 BarClass의 생성자를 호출했음에도 불구하고 타입이 부모 클래스의 포인터인 FooClass*이다

컴파일러는 이 정적 타입 (FooClass*) 만을 보고 ‘아 얘는 FooClass이구나' 라고 판단하여 FooClassfoo 함수를 호출하게 된다

class FooClass {
    public:
        virtual void foo(void) {
            std::cout << "foo\n";
        }
};

class BarClass: public FooClass {
    public:
        void foo(void) {
            std::cout << "bar\n";
        }
};

FooClass* foo = new FooClass();
FooClass* bar = new BarClass(); // 타입이 부모 클래스의 포인터

foo->foo(); // foo
bar->foo(); // foo

위의 잘못된 예시를 virtual 키워드를 이용하여 바로잡으면 foo 함수는 동적 바인딩이 되며, 결과적으로 부모 클래스의 foo 대신 자식 클래스에서 오버로딩된 foo를 가져오게 된다

정적 타입, 동적 타입

  • 정적 타입 언어는 컴파일 시에 타입이 결정된다
    • C, C++, C#, Java 등
    • 결정된 타입 (자료형) 에 맞지 않는 값이 변수에 들어올 경우 컴파일 에러가 발생한다
    • 타입 관련 문제점을 초기에 발견할 수 있다
  • 동적 타입 언어는 런타임 (실행 시) 에 타입이 결정된다
    • Javascript, Python, Ruby 등
    • 타입을 굳이 붙이지 않고 변수를 선언한다
    • 변수의 내용물 (값) 에 의해 타입이 결정된다
    • 예상치 못한 타입 오류가 발생할 여지가 많아진다

정적 바인딩, 동적 바인딩

정적 바인딩(Static binding) vs. 동적 바인딩(Dynamic binding)

  • 정적 바인딩은 컴파일 단계에서 함수나 변수의 성질과 내용 등이 결정된다
    • 함수를 호출하면 메모리상의 어느 주소로 점프해야 할 지 결정하여 바인딩한다
    • 런타임 때는 해당 주소로 점프해서 내용을 바로 호출한다
  • 동적 바인딩은 런타임 단계에서 함수나 변수의 성질과 내용 등이 결정된다
    • 포인터 바인딩을 미뤄두고 공간만 마련해 두었다가, 런타임 시에 결정한다
    • 메모리 공간의 낭비 및 타입 체킹으로 인한 속도 저하가 발생한다

순수한 가상 함수와 추상 클래스

코딩교육 티씨피스쿨

virtual void foo() const = 0;

순수한 가상 함수 (Pure Virtual Function) 란, 가상함수를 선언하되 본체를 선언하지 않는다

이 함수는 본체가 없기 때문에 무조건 자식 클래스에서 오버라이딩을 해주어야 사용할 수 있다

순수한 가상 함수를 하나라도 포함한 클래스를 추상 클래스 (Abstract Class) 라고 하며, 추상 클래스는 동작이 정의되지 않은 멤버 함수를 포함하고 있기 때문에 인스턴스를 만들 수 없다

모든 순수한 가상 함수를 자식 클래스에서 오버라이딩해야만 내용이 존재하므로 컴파일이 된다

추상 클래스의 포인터와 참조는 사용할 수 있다 (인스턴스만 생성할 수 없을 뿐)

'42 > 42s Cursus' 카테고리의 다른 글

[Rank 4] CPP 06  (0) 2022.09.17
[Rank 4] CPP 05  (0) 2022.09.11
[Rank 4] CPP 03  (0) 2022.09.11
[Rank 4] CPP 02  (0) 2022.09.11
[Rank 4] CPP 00 ~ 01  (0) 2022.09.10
Comments