본문 바로가기
  • 쓸쓸한 개발자의 공부방
C++

[C++] 상속과 RTTI

by 심찬 2021. 12. 30.

 

 

상속과 RTTI

 

함수가 인자로 기반 클래스의 포인터를 받으면

- 기반 클래스 뿐 아니라 모든 파생 클래스를 전달 받을 수 있다.

 

기반 클래스 포인터로 파생 클래스의 고유 멤버에 접근 할 수 없다.

- 파생클래스의 고유 멤버에 접근하려면 파생 클래스 타입으로 캐스팅 (다운 캐스팅, downcasting) 해야 한다.

#include <iostream>
#include <typeinfo>

class Animal {};

class Dog : public Animal 
{
public:
    int color;
};

void foo(Animal* p)
{
    // p가 Dog 이라면
    p->color = 10; // error
}

int main()
{
    Animal a; foo(&a);
    Dog    d; foo(&d);    
}

아래와 같이 p는 Animal 클래스 형태로 선언되었으므로 color 정보가 없다. 아래와 같은 에러를 만날 수 있다.

main.cpp:15:8: error: ‘class Animal’ has no member named ‘color’
   15 |     p->color = 10; // error
      |        ^~~~~

 

상속관계에서 사용하는 RTTI (typeid)

 

typeid

가상함수가 없는 객체 (non polymorphic type) 컴파일 시간에 포인터 타입으로 조사

가상함수가 있는 객체 (polymorphic type) 실행시간 타입 조사 (가상함수 테이블 등을 사용)

 

 

<잘못된 사용 방법> 아래의 경우 *p를 컴파일 시간에 조사해서 Animal 로 출력됨.

#include <iostream>
#include <typeinfo>

class Animal {};

class Dog : public Animal 
{
public:
    int color;
};

void foo(Animal* p)
{
    const std::type_info& t = typeid(*p);
    std::cout << t.name() << std::endl;
}

int main()
{
    Animal a; foo(&a);
    Dog    d; foo(&d);    
}

 

실행시간 타입 조사를 위해 가상함수로 선언 되어 있어야 한다.

#include <iostream>
#include <typeinfo>

class Animal 
{
public:
    virtual ~Animal() {} // 기반 클래스이 소멸자는 반드시 가상함수여야 한다.
};

class Dog : public Animal 
{
public:
    int color;
};

void foo(Animal* p)
{
    //const std::type_info& t = typeid(p);
    const std::type_info& t = typeid(*p);
    std::cout << t.name() << std::endl;
    
    if ( typeid(*p) == typeid(Dog))
    {
        Dog* pDog = static_cast<Dog*>(p);  // 다운 캐스팅
        pDog->color = 10; // 멤버 함수 접근 가능
        std::cout << "Dog" << std::endl;
    }
}

int main()
{
    Animal a; foo(&a);
    Dog    d; foo(&d);    
}


 

upcasting VS downcasting

 

upcasting : 파생 클래스 포인터를 기반 클래스 타입으로 캐스팅 하는 것 (항상 안전함)

downcasting : 기반 클래스 포인터를 파생클래스 타입으로 캐스팅하는 것 (안전하지 않을 수도 있다.)

 

downcasting과 캐스팅 연산자

 

static_cast : 잘못된 downcasting 조사할 수 없다.

 - 단, 컴파일 시간에 캐스팅을 수행하므로 오버헤드가 없다.

dynamic_cast : 잘못된 downcasting 을 하면 0을 반환 한다.

 - 실행시간에 캐스팅을 수행하므로 약간의 오버헤드가 있다.

#include <iostream>
#include <typeinfo>

class Animal 
{
public:
    virtual ~Animal() {}
};

class Dog : public Animal 
{
public:
    int color;
};

void foo(Animal* p)
{
    Dog* pD = static_cast<Dog*>(p);
    std::cout << "1) " << pD << std::endl;
    // 잘못된 downcasting을 조사할 수 없다.
    
    Dog* pDog = dynamic_cast<Dog*>(p);
    
    if ( pDog != 0 )
    {
        std::cout << "2-1) " << pDog << ", color:" << pDog->color << std::endl;
        pDog->color = 10;
        std::cout << "2-2) " << pDog << ", color:" << pDog->color << std::endl;
    }
    std::cout << "2) " << pDog  << std::endl;
}

int main()
{
    Animal a; foo(&a);
    std::cout << "==============================" << std::endl;
    Dog    d; foo(&d);    
}

 

 

댓글