[Modern C++] 항목 8: 0과 NULL보다 nullptr을 선호하라

게시:     수정

카테고리:

태그: ,

이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역

가독성이 떨어지는 직역들을 수정하며 정리하였습니다. e.g. 연역 → 추론, 중복적재 → 오버로딩

📦 3. 현대적 C++에 적응하기

👉🏻 항목 8: 0과 NULL보다 nullptr을 선호하라

// 3가지 함수 오버로딩
void f(int);
void f(bool);
void f(void*);

f(0); // f(int) 호출

// 컴파일 안될 가능성이 있으며, 보통은 f(int) 호출
// f(void*) 호출하는 경우는 없음
f(NULL);

f(nullptr); // f(void*) 호출
  • long → int, long → bool, 0L → void* 변환은 우선순위가 동일하다.
  • 0L(리터럴 0)과 NULLint이다.
    • 포인터만 사용할 수 있는 위치에 0이 있다면, null 포인터로 해석한다!
    • NULLlong과 같은 정수 형식이 되는 경우도 희귀하게 존재한다.
  • nullptr은 정수/포인터 형식이 아닌, 모든 형식의 포인터이다.
    • 실제 형식은 std::nullptr_t이다.
    • 정수 형식으로 해석되지 않는다.
  • 포인터/정수 형식에 대한 오버로딩은 피해야 한다.
    • nullptr이 좋지만, 0/NULL을 사용하는 개발자들이 여전히 있기 때문이다.

모든 형식의 포인터라고 부르는 이유:

  • nullptr_traw 포인터 형식으로 암묵적 변환된다.

🔎 nullptr의 모호성 해결

// 반환형이 정수/포인터 중 무엇인지 알 수 없다면?
auto result = findRecord(...);

// 이것은 포인터인가, 정수인가?
if(result == 0) { ... }

// 모호성(ambiguity)가 없음!
if(result == nullptr) { ... }

🔧 nullptr과 템플릿

템플릿화 하기 전:

int f1(shared_ptr<Widget> spw);
double f2(unique_ptr<Widget> upw);
bool f3(Widget* pw);

std::mutex f1m, f2m, f3m;
using MuxGuard = std::lock_guard<std::mutex>;

{
	MuxGuard g(f1m);
	auto result = f1(0);
}

{
	MuxGuard g(f2m);
	auto result = f2(NULL);
}

{
	MuxGuard g(f3m);
	auto result = f3(nullptr);
}

템플릿화 (C++11):

int f1(shared_ptr<Widget> spw);
double f2(unique_ptr<Widget> upw);
bool f3(Widget* pw);

template<typename FuncType,
				 typename MuxType,
				 typename PtrType>
auto lockAndCall(FuncType func,
							 MuxType mutex,
								 PtrType ptr) -> decltype(func(ptr))
{
	using MuxGuard = std::lock_guard<MuxType>;
	
	MuxGuard g(mutex);
	return func(ptr);
}

템플릿화 (C++14): 함수의 반환 형식에만 차이가 있다.

int f1(shared_ptr<Widget> spw);
double f2(unique_ptr<Widget> upw);
bool f3(Widget* pw);

template<typename FuncType,
				 typename MuxType,
				 typename PtrType>
decltype(auto) lockAndCall(FuncType func,
								 MuxType mutex,
								 PtrType ptr)
{
	using MuxGuard = std::lock_guard<MuxType>;
	
	MuxGuard g(mutex);
	return func(ptr);
}
auto result1 = lockAndCall(f1, f1m, 0);       // ① ⚠️ 오류!
auto result2 = lockAndCall(f2, f2m, NULL);    // ② ⚠️ 오류!
auto result3 = lockAndCall(f3, f3m, nullptr); // ③ ✅
  • ①②에서 0/NULL은 템플릿 형식 추론을 통해 int이 된다.
    • shared_ptr, unique_ptr 매개변수와 호환되지 않으므로 에러가 발생한다.
  • ③에서 nullptr →(추론) nullptr_t →(암묵적 변환) Widget* 순서로 처리된다.

🧐 정리

  • 0NULL보다 nullptr을 선호하자.
  • 정수/포인터 형식에 대한 오버로딩을 피하자.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기