ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ Anything ] - Object Slicing
    C++ 2021. 5. 20. 19:41

    Object Slicing

    다음과 같은 짧은 코드가 있다고 가정 해봅시다.

    언뜻 보기에는 똑같은 결과가 나올 것 같습니다. 

    실행결과는 그렇지 않습니다. reference로 받은 함수는 정상적으로 모든 정보를 출력해주었으나 call by value로 받은 함수는 age만 제대로 출력이 된 것을 확인할 수 있습니다. 

     

    Virtual table and Virtual pointer

     

    먼저 vtbl과 vptr이 무엇인지 살펴보아야 합니다. 컴파일러는 이 가상함수들을 다룰 때 각 object들에 우리가 모르는 사이에 어떤 포인터를 달아서 관리합니다. 그리고 이 포인터들은 함수들의 주소가 보관되어 있는 static array로 이어지며 Compile time에 생성되는 이 배열에는 가상함수테이블[Virtual Function Table, vtbl]이라는 이름이 있습니다. 그리고 이 가상함수테이블은 가상함수를 포함한 모든 class에 부여되며 선언된 가상함수의 주소가 있습니다.

    Base 클래스엔 vptr가 존재하며 이는 Base 클래스를 상속받는 모든 클래스에 상속되며 해당 클래스가 가상함수를 포함한다면 생성된 가상함수테이블을 가리키게 됩니다. 따라서 가상함수를 포함하는 모든 클래스는 가상함수테이블을 위한 포인터 만큼 크기가 더 큽니다. 여기서 Base인 Man에는 Profile()이라는 가상함수가 있었습니다. 그리고 Derived인 Stan 클래스엔 이 Profile()을 오버라이드[Override]한 함수가 있습니다. Man 클래스의 vptr은 Man 클래스를 위한 가상함수테이블을 가리키고 이 가상함수테이블에는 Man::Profile()을 가리키는 포인터가 있을 겁니다. 같은 식으로 Stan 클래스엔 Stan 클래스의 가상함수테이블을 가리키는 vptr이 있을 것이고 해당 가상함수테이블에는 Stan::Profile()을 가리키는 포인터가 존재합니다. 여기서 만약 Stan이 Man의 Profile()을 오버라이드하지 않았더라면 해당 가상함수테이블의 함수 포인터는 Stan이 아닌 Man의 Profile()을 가리킵니다. 즉, 가상함수테이블의 함수 포인터는 가장 깊이 상속된( Base에서 가장 멀리 떨어진 ) 클래스의 함수를 가리키도록 되어 있습니다.

    이제, Base class였던 Man으로 다시 돌아갑니다. 이 Base 클래스는 컴파일러가 슬쩍 달아주는 포인터가 있고 이 포인터는 Base클래스의 모든 가상함수들의 주소에 해당하는 가상함수테이블로 이어지고 있습니다. Call by value의 호출을 하면 argument로서 Base class의 reference가 아니라 복사생성자가 호출되면서 Man 부분 만큼 복사본이 올라갑니다. 완전히 똑같은 Man을 복사하면 vptr로 Stan에 접근할 수 있을 것 같습니다. 하지만 복사생성자가 Base 클래스인 Man의 가상함수테이블을 생성한다는 점이 문제입니다. 이렇게되면 vptr[Virtual Table Pointer]를 죽을 때까지 호출해도 Derived 클래스인 Stan의 멤버에 접근할 수가 없습니다. 따라서, Call by Value 호출 시, Man의 가상함수테이블이 생성된 것이고 이에 따라 Stan의 멤버들이 잘려버린 것입니다. 바로 이 부분을 Slicing이라고 일컫습니다. 정말 Copy constructor를 호출하는지 확인을 해봅니다.

    안녕? 나는 복사생성자야!

    보시는 바와 같이 Call by value의 경우, 모두 복사생성자가 호출되었습니다. 반면에  Call by reference로 전달하는 경우에는 복사생성자가 호출되지 않았습니다. 그런데 함수의 parameter는 분명 Base class인 Man&이었습니다. 실제로 Argument로 넘어가는 것은 Stan 전체가 아니고 Base class인 Man만큼만 넘어갑니다. 그런데 각 클래스마다 고유한 가상테이블포인터[Virtual Table Pointer]가 있다고 했습니다. 이 포인터는 Reference이며 Base와 Derived class 모두 존재합니다. 한 클래스가 다른 클래스로부터 상속을 받을 때, 이 포인터도 함께 상속을 받으며 Base class의 가상함수테이블이 아닌 Derived Class의 가상함수테이블을 가리키고 있습니다. 따라서, 비록 Base class 만큼의 정보만 Argument로 넘어갔다고 하나 바로 이 포인터를 통해서 처음 인스턴스를 생성했을 때 함께 생성된 가상함수테이블을 참조할 수 있게 되어 정상적으로 정보가 출력된 것입니다!

    추가로 Derived Class가 Base Class의 함수를 Override 하지 않으면 가상함수테이블은 가상함수의 most-derived 함수를 참조하기 때문에 Base Class의 Profile()이 호출됩니다. 

     

    언제봐도 짜릿하네요

     

    Copy constructor and Copy assignment operator

    미묘한 차이가 있습니다. 다음과 같은 형태의 간단한 Class definition을 생각해봅니다. copy constructor는 different object로 같은 타입이면서 새로운 object를 initialise합니다. 반면에 Copy assignment operator는 같은 종류의 오브젝트를 다른 오브젝트에 복사할 때 사용합니다.

    이건 Copy assignment operator가 호출됩니다. 그러나 다음과 같이 쓰면 복사 생성자가 호출됩니다.

     

     

     

     

     

    ref

    Open Source ... - C++ Tutorial: Object Slicing and Virtual Table - 2020 (bogotobogo.com)

     

    Open Source ... - C++ Tutorial: Object Slicing and Virtual Table - 2020

    C++ Tutorial Object Slicing and Virtual Table - 2020

    18.6 — The virtual table | Learn C++ (learncpp.com)

    'C++' 카테고리의 다른 글

    [ Anything ] - Iterator - 2  (0) 2021.09.08
    [ Anything ] - Iterator - 1  (0) 2021.09.07
    [ Anything ] - Permutation and Combination  (0) 2021.08.29
    [2] - Templates - 2  (0) 2021.08.08
    [1] - Templates - 1  (0) 2021.08.05
Designed by Tistory.