프로그래밍 일반/C++ 프로그래밍

[Effective Modern C++] 항목 4: 연역된 형식을 파악하는 방법을 알아두라

지노윈 2020. 8. 22. 16:39
반응형

형식 연역의 결과를 직접 확인 하는 수단으로서

코드를 작성/수정하는 시점에서,

컴파일 시점에서,

실행 시점에서 

형식 연역 정보를 얻는 방법을 살펴보겠습니다.


IDE 편집기

IDE의 코드 편집기 중에서 프로그램 개체(변수, 매개변수, 함수 등) 위에 마우스 커서를 올리면 그 개체의 형식을 표시해 주는 것이 많습니다.

const int theAnswer= 42;

auto x = theAnswer;
auto y = &theAnswer;

IDE 편집기는 x의 연역된 형식이 int이고 y의 연역된 형식이 const int*임을 표시해 줄 것입니다. 컴파일 가능 상태여야 편집기에서 요청된 개체의 형식을 표시 할 수 있을 것입니다. 간단한 경우에는 IDE가 알려준 정보가 쓸만합니다. 그러나, 복잡한 형식에서는 그리고 도움이 되지 않을 수 있습니다.

 


IDE 편집기 : Visual Studio

많이 사용되는 편집기인 Visual Studio의 경우를 살펴봅시다. 먼저 옵션에서 C++환경에 대한 IntelliSense 기능이 켜져 있어야 합니다.

그러면, 다음과 같이 y 객체에 대한 연역된 형식을 마으스 커서 오버하면 자동으로 보여 줍니다.


컴파일러의 진단 메시지

컴파일 오류의 발생은 컴파일러가 연역한 형식을 파악하는 데 효과적인 방법입니다. 

template<typename T>
class TD;

이 템플릿을 인스턴스화하려 하면, 인스턴스화할 템플릿 정의가 없어서 컴파일 오류가 발생합니다.

x와 y의 형식을 알고 싶다면 해당 형식들로 TD를 인스턴스화해보면 됩니다.

const int theAnswer = 42;

auto x = theAnswer;
auto y = &theAnswer;

TD<decltype(x)> xType;
TD<decltype(y)> yType;

다음은 컴파일 오류 메시지의 내용입니다.

 error C2079:  'xType'은(는) 정의되지 않은 class 'TD<int>'을(를) 사용합니다.
 error C2079:  'yType'은(는) 정의되지 않은 class 'TD<const int *>'을(를) 사용합니다.

 

 


실행시점 출력

다음은 typeid와 std::type_info::name을 이용해 x와 y에 대한 연역된 형식을 출력하는 예입니다.

	const int theAnswer = 42;

	auto x = theAnswer;
	auto y = &theAnswer;

	std::cout << typeid(x).name() << '\n';
	std::cout << typeid(y).name() << "\n";

다음과 같이 출력 됩니다.


실행 시점 출력, 믿을만 하지 않다

컴파일러 오류 메시지를 이용해서 이 x와 y의 형식들에 대한 정확한 결과를 얻었다고해서 해서 형식 보고 문제가 완전히 해결되었다고 생각하지 말아야 합니다. 좀더 복잡한 예를 살펴 봅시다.

template<typename T>
void f(const T& param)
{
	using std::cout;
	cout << "T =     " << typeid(T).name() << "\n";
	cout << "param = " << typeid(param).name() << "\n";
}

std::vector<Widget> createVec()
{
	std::vector<Widget> a;

	Widget widget;
	a.push_back(widget);
	return a;
}

int main() 
{
	const auto vw = createVec();

	if (!vw.empty()) {
		f(&vw[0]);
	}
	return 0;
}

gcc와 clang에서는 다음 출력 결과는 다음과 같습니다.

PK는 "const를 가리키는 포인터"를 뜻하고 6은 클래스 이름(Widget)의 글자 수 입니다.

Visual Studio에서는 다음과 같이 출력됩니다.

템플릿 f에서 param의 선언된 형식은 const T&이다. 그런데도 T와 param의 형식이 같다는 것은 좀 이상해 보입니다. 예를 들어 T가 int 였다면 param의 형식은 이와 다른 const int&가 되어야 합니다.

안타깝게도 std::type_info::name의 정보는 믿을 만하지 않습니다.

 

형식을 준수 하려면 이렇게 틀린 보고를 할 필요가 있습니다. 항목 1에서 설명 했듯이 값 전달의 경우 만일 형식이 참조이면 참조성이 무시되며, 참조를 제거 후의 형식이 const이면 해당 const 성 역시 무시 됩니다. vm의 형식이 const auto이므르 &vm[0]는 const widget*이며, param는 const Widget* const&입니다. 참조를 제거한 후의 형식이 const이므로 const 성 역시 무시되어 const Widget*로 보고된 것입니다.


IDE 출력, 믿을만 하지 않다

마찬가지로 안타까운 일은, IDE 편집기가 표시하는 형식 정보 역시 믿을만 하지 않습니다. 

어떤 IDE 편집기는 T의 형식을 다음과 같이 보합니다.

const std::_Simple_types<std::_Wrap_allc<std::Vec_base_types<Widget, 
	std::allocator<Widget> > :: _Alloc>::value_tpe>::value_type *

그리고 param의 형식을 다음과 같이 보고합니다.

const std::_Simple_types<...>::value_type *const &

T에 대한 형식 보다는 덜 복잡하지만, 중간의 ...가 좀 혼동될 것입니다. 


Boost.TypeIndex 제대로 출력

행운 보다는 라이브러리를 더 믿는 분이라면, std::type_info::name과 IDE가 실패하는 경우에서도 Boost TypeIndex 라이브러리는 성공하도록 설계 되어 있다는 점을 반길 것입니다.

다음은 Boost.TypeIndex를 이용해서 타입을 얻는 방법을 보여주는 예제입니다.

template<typename T>
void f(const T& param)
{
	using std::cout;
	using boost::typeindex::type_id_with_cvr;
    
	cout << "T =     " << type_id_with_cvr<T>().pretty_name() << "\n";
	cout << "param = " << type_id_with_cvr<decltype(param)>().pretty_name() << "\n\n";
}

출력의 결과는 다음과 같습니다. (Wandbo를 이용해 테스트 함)

IDE 편집기와 컴파일러 오류 메시지, 그리고 Boost.TypeIndex같은 라이브러리는 단지 컴파일러가 연역하는 형식을 파악하는데 도움이 되는 도구일 뿐임을 명심하면 좋겠습니다. 항목 1, 항목 2항목 3의 연역 규칙들을 숙지하는 것보다 나은 것이 없습니다.


기억해 둘 사항들

  • 컴파일러가 연역하는 형식을 IDE 편집기나 컴파일러 오류 메시지, Boost TypeIndex 라이브러리를 이용해서 파악할 수 있는 경우가 많다.
  • 일부 도구의 결과는 유용하지 않고 정확하지도 않을 수 있으므로, C++의 형식 연역 규칙들을 제대로 이해하는 것이 여전히 필요한 일이다.