cout.setf(ios_base::hex); // 1. 잘못된 방법
cout.setf(ios_base::hex, ios_base::basefield); // 2. 올바른 방법
C++의 표준 라이브러리에서 출력 서식 중 진법을 바꾸려면, 2번과 같이 코드를 작성해야 합니다.
하지만 1번과 같이 작성해도 컴파일 에러나 런타임 에러를 발생시키지 않습니다.
그러면 왜 1번 방법처럼 쓸 수 없는지, 쓸 경우 문제점은 무엇이 있는지가 궁금해 조사해보았습니다.
레퍼런스에 의하면 ios_base
의 fmtflags
는 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
뿐만 아니라, adjustfield
와 floatfield
에도 똑같이 적용됩니다.