[C++] UpCasting & DownCasting

출처
ㅎㅇgdprgmer 블로그 링크

포인터

주소를 저장하는(가리키는) 변수

변수의 생성주기 때문에 사용

  • Stack
    • 변수(지역변수, 함수의 복귀주소 (RET), 매개변수)는 기본적으로 Stack에 할당됨
    • 생성된 코드 블록 내에서만 유효
int* GetAddress() 
{ 
    int num = 10;       // 지역변수 선언 
    int* pNum = #   // 지역변수 선언 및 초기화 
    
    return pNum;        // pNUm이 가지는 지역변수 num의 주소를 반환 
} 

int main(void) 
{ 
    int* pNum = GetAddress();         // 반환된 주소로 초기화 
    std::cout << *pNum << std::endl;  // 제어권을 잃은 상태로서, 반환된 주소에 해당하는 메모리에 남은 값을 출력 (언제 쓰레기 값이 될 지 모름)
    return 0; 
}
  • Heap
    • 런타임 중에 동적으로 할당한 변수들이 저장되는 공간
    • 프로그램과 생명주기를 함께 함
    • 프로그래머가 직접 new와 delete 연산자로 메모리 할당과 해제를 관여할 수 있다
int* GetAddress() 
{ 
    int* pNum = new int(5); // int 타입의 5를 가지고 있는 메모리를 pNum에 동적할당 
    
    return pNum;            // pNum이 가리키고 있는 주소 반환 
 } 
 
int main(void) 
{ 
    int* pData = GetAddress();           //pNum이 가지는 주소로 pData 초기화 
    std::cout << *pData << std::endl;    //pData가 가리키는 주소에 해당하는 값 출력 (5)
    
    int* pNumAddr = pData;               //pNumAddr도 pData와 같은 주소를 가리킴(참조카운팅++) 
    
    delete pData;                        //pData가 가리키는 주소에 해당하는 메모리 해제 
    std::cout << *pNumAddr << std::endl; //쓰레기 값 출력 
    return 0; 
}

nullptr 체크

int* GetAddress() 
{ 
    int* pNum = nullptr; return pNum; 
} 

int main(void) 
{ 
    int* pNum = GetAddress(); 
    if (pNum) 
    	std::cout << *pNum << std::endl; else std::cout << "nullptr" << endl; 
        
    return 0; 
}

상속관계의 객체들은 서로를 가로지르는 타입 캐스팅이 가능하다

업캐스팅

  • 기본 클래스 포인터로 파생클래스 객체를 가리키는 것
  • 그냥 파생클래스 객체를 선언해서 가져다 쓰면 되지 않는가…?
  • 다형성을 이용해서 코드 재사용성을 높인다
// 예제 코드
class CBase 
{
    public: virtual void Show() 
    { 
    	std::cout << "CBase" << std::endl; 
    } 
}; 

class CDerived1 : public CBase 
{ 
    public: virtual void Show() 
    { 
    	std::cout << "CDerived1" << std::endl; 
    } //overriding 
}; 
    

class CDerived2 : public CBase
{ 
    public: virtual void Show() 
    {
    	std::cout << "CDerived2" << std::endl; 
    } //overriding 
};

Casting_res

위 결과를 내기 위한 3가지 Solution

// 1) 각 파생클래스 객체를 선언하여 사용
main int main(void) 
{
    CBase base; 
    CDerived1 derived1; 
    CDerived2 derived2; 
    
    base.Show(); 
    derived1.Show();
    derived2.Show(); 
    
    return 0; 
}
// 2) 포인터, 동적바인딩을 활용
main int main(void) 
{ 
    CBase* pInterface = nullptr; 
    CBase* pBase = new CBase; 
    CDerived1* pDerived1 = new CDerived1; 
    CDerived2* pDerived2 = new CDerived2; 
    
    pInterface = pBase; 
    pInterface->Show(); 
    
    pInterface = pDerived1; 
    pInterface->Show(); 
    
    pInterface = pDerived2; 
    pInterface->Show(); 
    
    delete pBase; 
    delete pDerived1; 
    delete pDerived2; 
    return 0; 
}
// 3) 업캐스팅
main int main(void) 
{ 
    std::string classNames[3] = {"CBase", "CDerived1", "CDerived2" }; 
    std::string className = ""; 
    std::map<std::string, CBase*> mapData; 
    
    CBase* pInterface = nullptr; 
    CBase* pBase = new CBase; 
    CDerived1* pDerived1 = new CDerived1; 
    CDerived2* pDerived2 = new CDerived2; 
    
    mapData.insert(std::make_pair(classNames[0], pBase));
    mapData.insert(std::make_pair(classNames[1], pDerived1));
    mapData.insert(std::make_pair(classNames[2], pDerived2)); 
    
    for (int idx = 0; idx < 3; idx++) 
    { 
		className = classNames[idx]; 
        pInterface = mapData.find(className)->second; //업 캐스팅 pInterface->Show(); 
    } 
    
    mapData.clear(); 
    
    delete pBase; 
    delete pDerived1; 
    delete pDerived2; 
    
    return 0;
}

  • 객체를 동적할당을 통해 힙에 생성하고, 주소를 알고 있으면 됨.
  • 단 한번의 객체 생성으로 map에 해당 클래스의 이름과 함께 객체의 주소를 저장 (초기화)
  • 이 map을 어디서든 가져다 쓸 수 있게 만든다면 (싱글톤 & 팩토리 패턴) 기본 클래스 타입의 포인터 변수만 선언하고 그 포인터 변수로 업캐스팅하게 되면서 for문 속 단 세줄로 효율적으로 구현 가능

다운캐스팅

  • 동일한 타입의 포인터가 동일한 타입을 가리키는 것
  • 업캐스팅을 하게 되면 기본 클래스에 정의된 멤버만 호출할 수 있기 때문에 파생클래스 고유의 기능을 사용할 수 없다

기본클래스 포인터->파생클래스 객체 ==> 파생클래스 포인터->파생클래스 객체

  • 이 때 파생클래스는 서로 동일한 타입이여야 하며, 명시적 형변환을 해야한다
class CBase 
{
    public: virtual void Show() 
    { 
		std::cout << "CBase" << std::endl; 
    } 
    
    void A() 
    { 
    	std::cout << "A의 고유 기능" << std::endl; 
    }
}; 


class CDerived1 : public CBase 
{ 
	public: virtual void Show() 
    { 
    	std::cout << "CDerived1" << std::endl; 
    } 
    
    void B() 
    { 
    	std::cout << "B의 고유 기능" << std::endl; 
    } 
}; 


class CDerived2 : public CBase 
{ 
	public: virtual void Show() 
    { 
    	std::cout << "CDerived2" << std::endl; 
    } 
    
    void C() 
    { 
    	std::cout << "C의 고유 기능" << std::endl; 
    } 
};
int main(void) 
{
    std::string classNames[3] = { "CBase", "CDerived1", "CDerived2" }; 
    std::string className = ""; 
    std::map<std::string, CBase*> mapData; 
    
    CBase* pInterface = nullptr; 
    CBase* pBase = new CBase; 
    CDerived1* pDerived1 = new CDerived1; 
    CDerived2* pDerived2 = new CDerived2; 
    
    mapData.insert(std::make_pair(classNames[0], pBase)); 
    mapData.insert(std::make_pair(classNames[1], pDerived1)); 
    mapData.insert(std::make_pair(classNames[2], pDerived2)); 
    
    for (int idx = 0; idx < 3; idx++) 
    { 
    	className = classNames[idx]; 
        pInterface = mapData.find(className)->second; //업 캐스팅 
        
        switch (idx) 
        { 
        case 0: 
            pBase = (CBase*)pInterface;         //다운 캐스팅(pInterface를 CBase로 타입 캐스팅) 
            pBase->A(); 
            break; 
        case 1: 
            pDerived1 = (CDerived1*)pInterface; //다운 캐스팅 
            pDerived1->B(); 
            break; 
        case 2: 
            pDerived2 = (CDerived2*)pInterface; //다운 캐스팅 
            pDerived2->C(); 
            break; 
        default: 
            break; 
        } 
    } 
    
    mapData.clear(); 
    
    delete pBase; 
    delete pDerived1; 
    delete pDerived2; 
    
    return 0; 
}
  • 위 다운 캐스팅에서 쓰인 형변환은 C타입 캐스팅
    • 어떠한 제약 조건도 없이 프로그래머가 지정한 타입으로 강제 캐스팅 -> 실수가 있으면 바로 에러
  • 모던 C++에서는 RTTI(Run Time Type Information)와 함께 안전하고 강력하고 다양한 방식의 타입 캐스팅을 지원

© 2023 Jinsoo Lee. All rights reserved.

Powered by Hydejack v9.1.6