[GeeksForGeeks C++ 문제풀이] Virtual Functions(가상 함수)

Jin Hur·2022년 10월 27일
0

C++

목록 보기
18/18

source: https://www.geeksforgeeks.org/virtual-destructor/

Q1. 가상 함수 특징

  • 가상 함수는 파생 클래스에서 오바라이딩할 수 있는 함수이다.
  • 가상 함수는 계층 구조에서 런타임 다형성을 실현시켜준다.
  • 기본 클래스에서 함수가 가상 함수일 경우 포인터 또는 참조의 선언된 형식에 관계없이 참조된 객체의 실제 형식에 따라(가장 가까운 가상 함수 구현 클래스의) 구현된 함수를 호출한다. => 동적 바인딩
    • 반면 비가상 함수에서 함수는 참조 또는 포인터의 유형에 따라 호출된다. => 정적 바인딩

Q2. 가상 함수 호출

  • 포인터나 참조변수로 참조되고 있는 객체의 가상 함수를 호출한다.
  • 포인터나 참조변수의 타입이 아닌 실제 객체의 함수가 호출

Q3. 가상 함수 호출2

처음 show() 호출은 파생 클래스 객체의 오버라이딩된 가상 함수를 호출하는 것이고, 두 번째 show() 호출은 기본 클래스의 가상 함수가 호출된 것이다.


Q4. 순수 가상 함수

  • Pure virtual methods typically have a declaration (signature) and no definition (implementation).
    • 전형적으로 순수 가상 함수를 선언한 클래스에선 구현을 하지 않는다.
  • Although pure virtual methods typically have no implementation in the class that declares them, pure virtual methods in some languages (e.g. C++ and Python) are permitted to contain an implementation in their declaring class, providing fallback or default behaviour that a derived class can delegate to, if appropriate.
    • 일반적으로 순수 가상 함수는 이를 선언하는 클래스에 구현이 없지만 C++를 포함한 일부 언어의 순수 가상 메서드는 선언 클래스에서 구현을 허용하여 파생 클래스가 위임받을 수 있도록 한다. 대체 또는 기본 동작을 제공한다.
  • 순수 가상 함수를 선언한 클래스는 추상 클래스이므로 이 추상 클래스는 직접적으로 객체를 가질 수 없다.

Q5. 순수 가상 함수는 객체를 선언

  • 순수 가상 함수를 갖는 추상 클래스로 객체를 생성할 수 없다.
  • 포인터 변수는 선언할 수 있다.

Q6. 순수 가상 함수를 상속받은 파생 클래스

파생 클래스에서 순수 가상 함수를 오버라이딩하지 않는다면 파생 클래스 또한 추상 클래스이다.


Q7. 순수 가상 함수를 상속받은 파생 클래스2

따라서 위와 같이 순수 가상 함수를 오버라이딩하면 구체 클래스가 되어 객체를 생성하고 런타임 다형성을 통해 오버라이딩한 함수가 호출될 수 있다.


Q8. 생성자는 가상 함수가 될 수 없다.

가상 생성자와 같은 것은 없다. 생성자를 가상으로 만드는 것은 생성의 책임을 다른 객체에 위임한다는 의미인데 이는 의미가 없다.


Q9. 가상 소멸자

가상 함수를 오버라이딩하는 파생 클래스 객체를 참조하는 포인터나 참조변수를 통해 해당 객체가 소멸된다면 이는 가상 소멸자가 호출되어야 한다. 가상 소멸자를 호출해야만 파생 클래스 객체의 자원까지 완벽하게 해제할 수 있다.


Q10. 기본/파생 클래스 생성자와 소멸자 호출 및 처리 순서

  • new Derived()가 호출되면서 파생 클래스 생성자가 호출 -> 부모 클래스 생성자 호출 및 처리 -> 파생 클래스 생성자 처리
  • delete Var이 호출되면서 파생 클래스의 가상 소멸자 호출 및 처리 -> 부모 클래스 소멸자 호출 및 처리

Q11. static 함수는 가상 함수가 될 수 없다.


Q12. 가상함수테이블(vtable)과 테이블을 가리키는 포인터(vptr)

클래스 A(기본 클래스)는 VPTR이라는 가상함수테이블을 가리키는 포인터를 가진다. (클래스 B에는 없다.)
이 VPTR은 클래스 크기에 추가된다.

가상함수테이블(V-table)

reference:

컴파일 시 가상함수가 정의된 클래스가 있다면, 이 클래스의 가상함수 테이블(v-table)을 만들어 'rdata' 영역에 기록한다.
그리고 특정 객체의 가상함수가 호출될 때에는 이 가상함수 테이블을 참조하여 함수가 호출된다(런타임시 결정).

class Parent{
    virtual void func1(){
        AAA
    }
    virtual void func2(){
        BBB
    }
    virtual void func3(){
        CCC
    }
    void func4(){
        DDD
    }
}

class Child : public Parent{
    virtual void func1(){
        childA
    }
    virtual void func3(){
        childC
    }
}

source: https://cosyp.tistory.com/228

객체가 생성되면 객체마다 공간이 생성된다. 이때 해당 공간에 각 객체이 함수 호출에서 참조해야할 가상테이블 주소가 해당 공간에 포함된다(객체의 공간에 첫 4바이트(또는 8바이트)에 존재).
위 그림에서 func1은 가상함수로써 부모클래스와 자식클래스가 참조해야할 함수 주소가 다르다는 것을 확인할 수 있다. 반면 func2는 가상함수이지만 오버라이딩되지 않은 함수임을 추측할 수 있다.

자식 클래스의 vtable은 부모 클래스의 vtable 값들이 그대로 복사되며, 다만 오버라이딩된 함수의 주소만 갱신된다.

가상함수 테이블에는 일반 멤버 함수에 대한 정보는 들어있지 않다.

런타임시 가상함수가 호출되면 호출된 가상함수가 어떤 객체로 부터 호출되었는지 파악하고, 이 객체의 vtable을 참조한다. 그리고 가상함수 종류에 따라 offset + vtable 첫번째 주소를 통해 호출한다(동적 바인딩).


Q13. 파생 클래스에서의 virtual 키워드 유무

B::fun()은 virtual 키워드를 사용하지 않더라도 가상이다.
클래스에 가상 함수가 있으면 모든 자손 클래스에서 동일한 선언을 가진 함수는 자동으로 가상이 된다.
따라서 B와 C에서 fun() 선언에 virtual 키워드가 없더라도 가상 함수이다.


Q14. 부모 클래스의 (가상)함수 접근하기 -> 범위 확인 연산자(::) 사용

기본 클래스의 함수는 가상 함수를 포함하여 범위 확인 연산자(scope resolution operator, ::)를 통해 접근할 수 있다.

0개의 댓글