배경

cout.setf(ios_base::hex); // 1. 잘못된 방법
cout.setf(ios_base::hex, ios_base::basefield); // 2. 올바른 방법

C++의 표준 라이브러리에서 출력 서식 중 진법을 바꾸려면, 2번과 같이 코드를 작성해야 합니다.

하지만 1번과 같이 작성해도 컴파일 에러나 런타임 에러를 발생시키지 않습니다.

그러면 왜 1번 방법처럼 쓸 수 없는지, 쓸 경우 문제점은 무엇이 있는지가 궁금해 조사해보았습니다.

서식 설정은 bitflag로 관리된다

레퍼런스에 의하면 ios_basefmtflags는 bitflag로 관리됩니다. hex, oct, dec가 각각 하나의 bit를 가집니다.

setf(flag)는 단순히 해당 bit를 켜기만 합니다. 따라서 아래와 같은 코드는 oct, dec, hex bit 모두가 켜져 있습니다.

이는 유효하지 않은 상태입니다. 출력 결과는 ios_base 구현체에서 출력할때 어떤 플래그를 먼저 확인하느냐에 따라 다르겠지만, 일단 코드만으로는 예측이 어렵습니다.

cout.setf(ios_base::oct);
cout.setf(ios_base::dec);
cout.setf(ios_base::hex);

cout << 42 << endl; // 42 출력

올바르게 진법 출력 형식 변경하기

그러면 “올바르게” 진법 서식을 바꾸려면, 기존의 진법 관련 bit들을 꺼야합니다. 이때 유용한 플래그가 ios_base::basefield입니다.

이 플래그로 masking 연산을 해주면 진법과 관련된 bit들을 모두 끌 수 있습니다.

fmtflags ios_base::basefield = dec | oct | hex

이제 setf(flag, mask) 를 한번 살펴봅시다.

아래 코드는 Visual Studio에서 F12를 눌러 확인한 setf(flag, mask) 메서드의 정의입니다.

_Mask 를 사용하여 기존 bit들을 끈 뒤, _Newfmtflags 로 bit들을 켜줍니다.

// Source: MSVC xiosbase header

fmtflags __CLR_OR_THIS_CALL setf(fmtflags _Newfmtflags, fmtflags _Mask) noexcept /* strengthened */
{
	// merge in format flags argument under mask argument
	const ios_base::fmtflags _Oldfmtflags = _Fmtfl;
	_Fmtfl                                = (_Oldfmtflags & ~_Mask) | (_Newfmtflags & _Mask & _Fmtmask);
	return _Oldfmtflags;
}

따라서 위 메서드를 사용하면 정상적으로 작동합니다.

이 문제는 basefield 뿐만 아니라, adjustfieldfloatfield 에도 똑같이 적용됩니다.