주간 팁 #141: bool
로의 암시적 변환에 주의하라
원래 게시일: 2018년 1월 19일 (TotW #141)
작성자: Samuel Freilich
최종 업데이트: 2020년 4월 6일
빠른 링크: abseil.io/tips/141
두 가지 널 포인터 검사 방법
널 포인터를 역참조하기 전에 검사는 충돌과 버그를 방지하기 위해 중요합니다. 이 검사는 두 가지 방법으로 수행할 수 있습니다:
if (foo) {
DoSomething(*foo);
}
if (foo != nullptr) {
DoSomething(*foo);
}
두 조건문의 의미는 foo
가 포인터인 경우 동일하지만, 후자의 경우 타입 검사가 조금 더 엄격합니다.
C++에서는 많은 타입이 bool
로 암시적 변환될 수 있으므로,
특히 포인터 대상 타입 자체가 bool
로 변환될 수 있는 경우에는 주의가 필요합니다.
다음 코드를 살펴보세요. 이 코드는 두 가지로 해석될 수 있습니다:
bool* is_migrated = ...;
// 이것은 `is_migrated`가 null이 아닌지 확인하는 것인가, 아니면
// 실제로 `*is_migrated`가 true인지 확인하려는 것인가?
if (is_migrated) {
...
}
이 코드는 더 명확하게 작성할 수 있습니다:
// bool*에 대한 null 포인터 검사를 수행합니다.
if (is_migrated != nullptr) {
...
}
Google C++ 스타일에서는 두 스타일 모두 허용됩니다.
기본 타입이 bool
로 암시적 변환되지 않는 경우 주변 코드 스타일을 따르세요.
스마트 포인터(std::unique_ptr
등)를 사용하는 경우에도 유사한 원칙이 적용됩니다.
선택적 값(Optional Values) 및 범위 제한
std::optional
같은 선택적 값을 사용할 때는 더욱 신중하게 고려해야 합니다.
예를 들어:
std::optional<bool> b = MaybeBool();
if (b) { ... } // 함수가 `std::optional(false)`를 반환하면 어떻게 될까요?
if
문에서 변수 선언을 하면 변수의 범위를 제한할 수 있지만,
값이 암시적으로 bool
로 변환되기 때문에 어떤 불리언 속성이 테스트되는지 명확하지 않을 수 있습니다.
다음과 같이 작성하면 의도를 더 명확하게 표현할 수 있습니다:
std::optional<bool> b = MaybeBool();
if (b.has_value()) { ... }
실제로 위 코드와 아래 코드는 동등합니다.
std::optional
의 bool
로의 변환은 optional
이 값이 있는지 여부만 확인하며, 내용물 자체는 확인하지 않습니다.
독자는 optional(false)
가 true
로 평가된다는 사실을 직관적으로 이해하기 어려울 수 있지만,
optional(false)
가 값을 가지고 있다는 사실은 명확합니다.
기본 타입이 bool
로 암시적 변환될 수 있는 경우에는 특히 주의가 필요합니다.
선택적 반환값을 사용하는 패턴
if
문에 변수 선언을 넣는 패턴은 변수의 범위를 제한할 수 있지만, 암시적 bool
변환을 포함합니다:
if (std::optional<Foo> foo = MaybeFoo()) {
DoSomething(*foo);
}
참고: C++17에서는 if
문에 초기화 구문을 포함할 수 있으므로,
변수의 범위를 제한하면서도 암시적 변환을 피할 수 있습니다:
if (std::optional<Foo> foo = MaybeFoo(); foo.has_value()) {
DoSomething(*foo);
}
“불리언과 유사한” 열거형
팁 #94의 권장사항에 따라 함수 시그니처에서 가독성을 위해 bool
대신 enum
을 사용했다고 가정해 봅시다.
이러한 리팩토링은 함수 정의에서 암시적 변환을 초래할 수 있습니다:
void ParseCommandLineFlags(
const char* usage, int* argc, char*** argv,
StripFlagsMode strip_flags_mode) {
if (strip_flags_mode) { // 이 값이 true일 때 어떤 의미인가요?
...
}
}
암시적 변환 대신 명시적 비교를 사용하면 더 명확해집니다:
void ParseCommandLineFlags(
const char* usage, int* argc, char*** argv,
StripFlagsMode strip_flags_mode) {
if (strip_flags_mode == kPreserveFlags) {
...
}
}
요약
bool
로의 암시적 변환은 명확성을 떨어뜨릴 수 있으므로, 보다 명시적인 코드를 작성하는 것이 좋습니다:
- 포인터 타입을 검사할 때는
nullptr
과 비교하세요.
(특히 포인터 대상 타입이 암시적으로bool
로 변환될 수 있는 경우) - 컨테이너 비어 있음을 검사할 때는
std::optional<T>::has_value()
같은 불리언 함수를 사용하세요.
(if
의 선택적 초기화 형식을 사용하여 변수 범위를 제한하세요.) - 열거형은 특정 값과 비교하세요.