본문 바로가기
프로그래밍/More Effective C++

항목5. 사용자 정의 타입변환 함수에 대한 주의를 놓지 말자

by 리뷰하는 (게임)프로그래머_리프TV 2011. 4. 28.


이번 항목은 타입변환에 대한 내용.

코드를 통해 문제점 부터 파악해 보자.

#include <iostream>

using namespace std;

class AAA
{
	int m_iNum1;

public:
	AAA( int num1 = 0, int num2 = 1 )	// 단일 인자 생성자
	{
		m_iNum1 = num1 / num2;
	}

	operator double() const;
};

AAA::operator double() const
{
	return static_cast<double>(m_iNum1);
}

void main()
{
	AAA a( 3, 2 );

	double d = 0.5 * a; // 아마 본래 의도는 이런 것이겠거니....

	cout << d << endl;

	// 그런데, 이게 왜 되는걸까?
	cout << a << endl;
}​




사실 코드만 보면 << 에 대한 연산자 오버로딩이 없기 때문에 안될거 같은데? 라고 생각하겠지만,
실제론 형변환이 이루어 지면서 출력이 된다.
이런 상황을 제거하기 위해, operator double가 아닌, 함수로써 변경을 하는 방법이 존재한다.

#include <iostream>

using namespace std;

class AAA
{
	int m_iNum1;

public:
	AAA( int num1 = 0, int num2 = 1 )	// 단일 인자 생성자
	{
		m_iNum1 = num1 / num2;
	}

//	operator double() const;

	double asDouble() const
	{
		return static_cast<double>(m_iNum1);
	}
};

//AAA::operator double() const
//{
//	return static_cast<double>(m_iNum1);
//}

void main()
{
	AAA a( 3, 2 );

//	double d = 0.5 * a;
	double d = 0.5 * a.asDouble();

	cout << d << endl;

	// 이젠 안된다.
//	cout << a << endl;

	// 복잡하다고 느낄 수 있지만,
	// string str;
	// str.c_str(); 을 생각해 보자
	// 같은 이유다.
	cout << a.asDouble() << endl;
}​



다음 예제를 보자.

#include <iostream>

using namespace std;

template< class T >
class Array
{
public:
	// 의도한 상황과 비슷한 연출을 위해
	// 어느정도 만들다 보니 본래 클래스의 역활은 거의 없다;
	// 단지 상황을 연출하기 위한 클래스 라고만 보자.
	T m_nArr[10];
	int m_nLow;
	int m_nHight;
public:
	Array(int lowBound, int highBound);
	Array(int size);

	T& operator[]( int index );
};

template< class T >
Array<T>::Array( int lowBound, int highBound )
{
	m_nLow = lowBound;
	m_nHight = highBound;
}

// 이번문제에 주 원인이 되는 생성자
template< class T >
Array<T>::Array( int size )
{
	m_nLow = 0;
	m_nHight = size;
}
template< class T >
T& Array<T>::operator[]( int index )
{
	return m_nArr[index];
}

bool operator==( const Array<int>& lhs, 
				const Array<int>& rhs )
{
	// 사실 여기서 무슨일을 하든 지금 상황과는 중요치 않다.
	for( int i=0; i<10; ++i )
	{
		if( lhs.m_nArr[i] != rhs.m_nArr[i] )
			return false;
	}
	return true;

}

void main()
{
	Array<int> a(10);
	Array<int> b(10);

	for( int i=0; i<10; ++i )
	{
		// 이제 문제다.
		// 내가 원한건 a[i] == b[i] 였고,
		// 지금 상황은 컴파일이 되지 않아야 정상이다.
		if( a == b[i] )	// 하지만 컴파일에서 문제 없음.
		{
			// 어떤 내용이 들어가 있는지는 현재로썬 중요치 않다
		}
		else
		{
			// 어떤 내용이 들어가 있는지는 현재로썬 중요치 않다
		}
	}
}​


다음과 같이, 암시적 변환을 사용하게 되었을 때는 원하지 않는 결과에 도출하게 된다.
물론 a == b[i]가 의도한 코딩이라면 상관없을지도 모르겠지만,
현재 상황에서 저 코드에 문제가 있다는 것을 시력이 좋거나,
런타임 후에 확인하거나,,
둘중 하나일 것이다.
코드를 하나하나 따라가 보면,
a 의 생성자에 10번,
operator==에 10번 들른다.
그 부분에 대해서는 항목 19에서 다시 설명한다고 한다.
자 문제는 파악 했고, 이제 저런 상황에서 우리가 원하지 않는 암시적 변환을 막고 싶다고 하였을 때,
어떻게 해야 할까? 답은 생각보다 쉬웠다.

explicit

	explicit Array(int size);  // 바로 이것!!!!​


이로써 이전에 if( a == b[i] ) 부분에서 컴파일 오류가 날것이다.
하지만 모든 문제가 해결된 것은 아니다.
비록 암시적 변환은 해결 됐지만,
명시적 타입 변환은 여전히 허용되는 것이다.

		if( a == Array<int>(b[i]) )	// 문제는 없다.
		if( a == static_cast< Array<int> >(b[i]) )	// 문제는 없는데,
		if( a == (Array<int>b[i] )	// 알 수 없는 거부감이 드는 코드들이다.​

현재로썬 저런 어색한 부분에 대한 해결책은 언급하고 있지 않지만,
명시적 타입 변환이라고 해도, 이전에 이야기 했듯이 지나친 형변환은 지양하자는 말이 떠오르는 코드다.
이번 타입 변환과는 별개로, 2번째 if문에 존재하는 > > 사이의 공백에 대해 언급을 하고 있는데,
저곳에 공백을 넣지 않으면 operator >> 를 호출 한다고 하니, 주의하도록 하자.
(필자는 그냥 무조건 공백을 주는 습관을 가지고 있으니 괜찮다!)
이 후에는 explicit 를 지원하지 않는 컴파일러에서의 해결 방법을 이야기 하고 있는데,
안봐도 되지만, 내용이 프록시 클래스 라는 것에 대해 미리 이야기 하고 싶었던것 같다.
한번 그냥 훑어 보는 정도로 넘어가면 될듯.