C++/Type Casting

C++ Type Castings

Soul-Learner 2016. 12. 28. 15:24

C++ 프로그래밍, 형 변환 ( Type Castings )


묵시적 형변환 ( Implicit Type Casting ) / 명시적 형변환 ( Explicit Type Casting )


묵시적 형변환이 권장되는 경우

unsigned char uc = 255;   

위의 경우에는, 정수의 리터럴은 4바이트인데, 2바이트 공간에 저장할 수는 없다. 그러므로 컴파일러는 묵시적으로 100 의 리터럴이 차지하는 4바이트 중에서 왼쪽 2바이트를 버리고 나머지 2바이트를 저장한다. 그래도 100 은 마지막 2바이트 안에 모두 포함될 만큼 작기 때문에 수치 손실은 발생하지 않는다. 그러므로 위와 같은 경우에는 묵시적 형변환이 자연스러우며 아무런 경고나 오류도 발생하지 않는다.

위의 경우에 명시적 형변환을 적용하면 다음과 같지만 권장되지는 않는다

  • unsigned char uc = (int) 255; // 혹은 아래처럼 한다
  • unsigned char uc = int (255);


묵시적 형변환이 권장되지 않는 경우

  • int i = 1000;
  • unsigned char uc = i;   // 묵시적 형변환, 비트손실, 수치손실 발생

묵시적 형변환을 하는 위의 코드에서도 오류는 방생하지 않으며 컴파일러에 따라서 컴파일러 경고는 있을 수 있다. 코드만 보고 개발자의 의도를 파악한다면 위의 코드는 의도된 형변환인지 실수인지 명확하지 않다. 그러므로 프로그램 전체를 해석하거나 디버깅하는데 시간이 더 걸릴 수 있다. 그래서 위와 같이 비트가 버려지면서 동시에 수치 손실도 발생하는 경우에는 분명한 개발자의 의도가 표현될 필요가 있으므로 명시적 형변환이 권장되는 것이다.


명시적 형변환이 권장되는 경우 

  • int i = 1000;
  • unsigned char uc = (unsigned char) i; // 비트가 잘리고 수치도 손실도 있는 경우
위의 경우에는 비트손실, 수치손실이 발생하는 경우이므로 개발자가 의도적으로 형변환했다는 표현으로 명시적 형변환을 사용한 것이다.

대부분의 기본형 데이터는 형변환할 때, 이와 같이 괄호를 사용하는 형변환 연산자를 사용하면 된다..


가상함수(Virtual Function)와 가상 소멸자(Virtual Destructor)

가상함수를 가진 클래스에 가상 소멸자가 없다면 다음과 같은 컴파일러 오류가 발생한다

Class 'Employee' has virtual method 'printInfo' but non-virtual destructor

가상함수(Virtual Function)를 선언한 클래스는 다른 클래스가 상속하여 오버라이드할 수 있다는 의미이며, 다형성을 활용하기 위해 자식 클래스의 객체를 생성하여 그 객체의 주소나 참조를 부모 클래스 형 포인터 변수나 참조변수에 저장하고 사용할 수도 있을 것이다. 이런한 경우에 그 포인터 변수를 사용하여 객체를 삭제하기 위해 delete 연산자를 사용하면 부모 객체 뿐만 아니라 런타임 객체인 자식객체도 제거될 수 있어야 한다. 이런 기능이 작동하기 위해서는 C++은 부모 클래스에 가상 소멸자(Virtual Destructor)를 필요로 한다. 그러므로 가상함수(Virtual Function)가 있는 클래스에는 가상 소멸자(Virtual Destructor)도 선언해 주어야 컴파일러 경고를 해제할 수 있다

가상함수와 가상 소멸자를 함께 선언한 예

#include <iostream>
#include <locale>

using namespace std;

class Employee {
	int empno;
	wstring ename;

	public:
	Employee(int empno, wstring& ename) {
		this->empno = empno;
		this->ename = ename;
	}
	virtual ~Employee(){} // 가상 소멸자 (Virtual Destructor)

	//가상함수(Virtual Function)
	virtual void printInfo(){
		wprintf(L"사번:%d, 이름:%s \n", empno, ename.c_str());
		//wcout << L"사번:" << empno << L", 이름:" << ename << endl;
	}
};

class Manager : public Employee {
	wstring field;

	public:
	Manager(int empno, wstring& ename, wstring& field):Employee(empno, ename){
		this->field = field;
	}

	// 상속되어 오버라이드한 가상함수
	void printInfo(){
		Employee::printInfo();  // 상위 클래스의 함수 호출
		wprintf(L"관리분야:%s \n", field.c_str());
	}
};

int main() {
    setlocale(LC_ALL, "");

    wcout << L"C++ 형변환(Type Casting)" << endl;

    wstring name(L"김인철");
    Employee emp(10,name);
    emp.printInfo();

    wstring mname(L"홍길동");
    wstring field(L"인사과");
    Manager mgr(20, mname, field);
    mgr.printInfo();

    return 0;
}


객체의 형변환

자식객체 -> 부모객체 방향 형변환(Up-Casting)은 IS-A 관계가 성립하므로 자연스러운 것이므로 묵시적 형변환되고, 그 반대는 성립하지 않기 때문에 묵시적 형변환시에 오류가 발생한다. 어떠한 객체든지 명시적 형변환은 컴파일 오류는 발생하지 않지만 런타임 객체와 형변환 목적 객체가 다르다면 런타임에 오류가 발생하며 실행되지 않는다.

  • IS-A 관계가 성립할 때만 객체의 형변환을 한다
  • IS-A 관계가 아니더라도 런타임 객체(실제 객체)와 형변환 목적 객체가 동일하다면 형변환을 해도 된다

객체의 형변환 예

#include <iostream>
#include <locale>

using namespace std;

class Employee {
	int empno;
	wstring ename;

	public:
	Employee(int empno, wstring& ename) {
		this->empno = empno;
		this->ename = ename;
	}
	virtual ~Employee(){} // 가상 소멸자 (Virtual Destructor)

	//가상함수(Virtual Function)
	virtual void printInfo(){
		wprintf(L"사번:%d, 이름:%s \n", empno, ename.c_str());
		//wcout << L"사번:" << empno << L", 이름:" << ename << endl;
	}
};

class Manager : public Employee {
	wstring field;

	public:
	Manager(int empno, wstring& ename, wstring& field):Employee(empno, ename){
		this->field = field;
	}

	// 상속되어 오버라이드한 가상함수
	virtual void printInfo(){
		Employee::printInfo();  // 상위 클래스의 함수 호출
		wprintf(L"관리분야:%s \n", field.c_str());
	}
};

int main() {
    setlocale(LC_ALL, "");

    wcout << L"C++ 형변환(Type Casting)" << endl;

    wstring name(L"김인철");
    Employee emp(10,name);

    wstring mname(L"홍길동");
    wstring field(L"인사과");
    Manager mgr(20, mname, field);

    // 클래스 객체의 묵시적 형변환(IS-A 관계라면 객체도 형변환할 수 있고 실행도 문제 없다)
    Employee e = mgr;		// 부모형으로 묵시적 형변환(Up-Casting)
    e.printInfo();   		// Employee 에 정의된 printInfo()가 실행됨(다형성을 보이지 않음)

    // 다형성(Polymorphism)은 객체의 포인터를 사용할 경우에만 확인할 수 있다
    Employee* pEmp = &mgr;	// 부모 포인터로 묵시적 형변환(Up-Casting)
    pEmp->printInfo();		// Manager 에 정의된 printInfo()가 실행됨(다형성을 보임)

    //Manager* pM = pEmp;			// IS-A 관계가 아니므로 부모형에서 자식형은 묵시적 형변환 안됨
    Manager* pM = (Manager*)pEmp;	// 형식상 IS-A관계가 아니면 명시적 형변환은 가능함.
    // 부모객체->자식객체의 묵시적 형변환은 오류(IS-A 관계 아님), 그러나 명시적 형변환은 가능함.
    // 부모객체->자식객체의 명시적 형변환은 가능하지만 실제 런타임 객체와 형변환 목적객체가 다르다면 런타임 오류발생
    // 위의 경우 e 변수에 저장된 실제 객체는 Manager객체이므로 e는 Manager형으로 형변환도 되고 런타임 오류가 발생하지 않는다
    pM->printInfo();

    return 0;
}


클래스의 객체나 객체의 포인터 변수는 형변환할 때 조금 더 많은 검사가 필요하므로 특별한 기능이 추가된 몇가지 형변환 연산자들이 사용된다

dynamic_cast<>() 형변환 연산자

새타입 변수 = dynamic_cast<새타입>(형변환 대상객체)  : 포인터와 참조변수에 대한 명시적 형변환에만 적용된다.

객체의 형변환시 IS-A관계인 경우와, 원래의 객체로 형변환하는 경우 이외에는 컴파일 경고를 통해 알려준다. 괄호를 사용한 형변환 연산자와의 차이점은 괄호를 사용한 형변환 연산자는 명시적 형변환을 하면 전혀 컴파일 경고가 발생하지 않다가 실행시에 오류가 발생하는 특성을 보이지만 dynamic_cast 를 사용하면 실행시에 오류가 발생할 수 있는 상황에서는 미리 컴파일시에 경고를 통해 알려준다는 점이 다르다. dynamic_cast 는 형변환에 실패하면 NULL 을 리턴하므로 반드시 검사 후에 사용하는 것이 바람직하다

NULL 포인터, void 포인터로 형변환도 지원되며, 참조변수도 형변환할 수 있는데 참조변수의 형변환에 실패할 경우에는 bad_cast 예외객체를 발생한다. 형변환 작업시 런타임에 검사하는 내용이 많아서 오버헤드가 발생한다는 점이 단점이다

#include <iostream>
#include <locale>

using namespace std;

class Employee {
	int empno;
	wstring ename;

	public:
	Employee(int empno, wstring& ename) {
		this->empno = empno;
		this->ename = ename;
	}
	virtual ~Employee(){} // 가상 소멸자 (Virtual Destructor)

	//가상함수(Virtual Function)
	virtual void printInfo(){
		wprintf(L"사번:%d, 이름:%s \n", empno, ename.c_str());
		//wcout << L"사번:" << empno << L", 이름:" << ename << endl;
	}
};

class Manager : public Employee {
	wstring field;

	public:
	Manager(int empno, wstring& ename, wstring& field):Employee(empno, ename){
		this->field = field;
	}

	// 상속되어 오버라이드한 가상함수
	virtual void printInfo(){
		Employee::printInfo();  // 상위 클래스의 함수 호출
		wprintf(L"관리분야:%s \n", field.c_str());
	}
};

int main() {
    setlocale(LC_ALL, "");

    wcout << L"C++ 형변환(Type Casting)" << endl;

    wstring name(L"김인철");
    Employee emp(10,name);

    wstring mname(L"홍길동");
    wstring field(L"인사과");
    Manager mgr(20, mname, field);

    // Manager*->Employee* 변환: IS-A 관계가 성립하므로 형변환에 전혀 문제가 없음
    Employee* pE = dynamic_cast<Employee*> (&mgr); // 컴파일, 런타임 전혀 문제 없음
    pE->printInfo();

    // Employee*->Manager* 변환: IS-A 관계는 아니지만 pE의 런타임 객체는 Manager이므로 전혀 문제 없음
    // pE의 실제 객체는 Manager이므로 pE->Manager 형변환은 컴파일시나 런타임에 전혀 문제가 없음
    Manager* pM = dynamic_cast<Manager*>(pE);
    pM->printInfo();

    // Employee*->Manager* 변환: IS-A 관계도 아니고 실제객체와 목적 객체가 다르므로 컴파일 경고 및 실행시 NULL 리턴
    //warning: dynamic_cast of 'Employee emp' to 'class Manager*' can never succeed
    Manager* pM2 = dynamic_cast<Manager*> (&emp); // IS-A 관계가 아님. 컴파일 경고. 실패해도 오류는 발생안함
    if(pM2!=NULL) {
    	pM2->printInfo();
    }else{
    	wcerr << L"형변환 오류" << endl;
    }

    return 0;
}


static_cast<새타입 포인터> (변환대상 객체 포인터)

dynamic_cast 와 유사하지만 실행시에 검사하는 내용이 많이 않아서 오버헤드를 줄일 수가 있다. dynamic_cast는 런타임에 형변환시 객체를 자세히 비교하여 형변환 가능성을 검사하는데 비해 static_cast 는 형변환 대상 객체의 최소한의 호환성을 검사하고 형변환을 수행한다. 특히 다음과 같은 경우에도 형변환에 성공하며 형변환된 객체를 실행해도 오류가 발생하지 않는다. 오버헤드를 줄일 수 있는 점은 장점이라고 할 수 있다. 객체의 형변환 뿐만 아니라 기본형 데이터의 자료형에 대한 형변환도 가능하다

Empmoyee emp = Employee();

Manager* pM = static_cast<Manager*> ( &emp );

pM->printInfo();  // Employee 에 정의된 printInfo() 가 실행됨

위의 경우에는 IS-A 관계도 아니고 형변환 대상객체와 목적객체가 다른데도 불구하고 형변환에 성공하는 점이 특징이다. 그러므로 형변환에 성공하더라도 쓸모가 있는지는 개발자가 검사해야 할 몫이다. 사실 위와 같이 형변환되더라도 실제 쓸모는 거의 없을 것이다. 왜냐면, 형변환된 객체는 Manager 객체의 속성과 함수를 가진 것이 아니라 여전히 Employee 객체의 속성과 함수가 작동하기 때문이다.

#include <iostream>
#include <locale>

using namespace std;

class Employee {
	int empno;
	wstring ename;

	public:
	Employee(int empno, wstring& ename) {
		this->empno = empno;
		this->ename = ename;
	}
	virtual ~Employee(){} // 가상 소멸자 (Virtual Destructor)

	//가상함수(Virtual Function)
	virtual void printInfo(){
		wprintf(L"사번:%d, 이름:%s \n", empno, ename.c_str());
		//wcout << L"사번:" << empno << L", 이름:" << ename << endl;
	}
};

class Manager : public Employee {
	wstring field;

	public:
	Manager(int empno, wstring& ename, wstring& field):Employee(empno, ename){
		this->field = field;
	}

	// 상속되어 오버라이드한 가상함수
	virtual void printInfo(){
		Employee::printInfo();  // 상위 클래스의 함수 호출
		wprintf(L"관리분야:%s \n", field.c_str());
	}
};

int main() {
    setlocale(LC_ALL, "");

    wcout << L"C++ 형변환(Type Casting)" << endl;

    wstring name(L"김인철");
    Employee emp(10,name);

    wstring mname(L"홍길동");
    wstring field(L"인사과");
    Manager mgr(20, mname, field);

    // Manager*->Employee* 변환: IS-A 관계가 성립하므로 형변환에 전혀 문제가 없음
    Employee* pE = static_cast<Employee*> (&mgr); // 컴파일, 런타임 전혀 문제 없음
    pE->printInfo();

    // Employee*->Manager* 변환: IS-A 관계는 아니지만 pE의 런타임 객체는 Manager이므로 전혀 문제 없음
    // pE의 실제 객체는 Manager이므로 pE->Manager 형변환은 컴파일시나 런타임에 전혀 문제가 없음
    Manager* pM = static_cast<Manager*>(pE);
    pM->printInfo();

    // Employee*->Manager* 변환: IS-A 관계도 아니고 실제객체와 목적 객체가 다르지만 형변환에 성공함
    Manager* pM2 = static_cast<Manager*> (&emp); // IS-A 관계가 아님. 컴파일 및 실행시 오류 없음
    if(pM2!=NULL) {
    	pM2->printInfo();  // Employee 에 선언된 printInfo()가 실행됨
    }else{
    	wcerr << L"형변환 오류" << endl;
    }

    return 0;
}


기타 형변환 연산자

reinterpret_cast, const_cast, typeid 등이 있다