일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 영화 리뷰
- 영화
- 오블완
- 게임
- Smart Pointer
- 참조자
- 메타테이블
- 함수 객체
- virtual function
- 암시적 변환
- lua
- reference
- 예외
- operator new
- 비교 함수 객체
- exception
- effective modern c++
- 반복자
- resource management class
- c++
- effective stl
- implicit conversion
- more effective c++
- 티스토리챌린지
- 다형성
- 언리얼
- 상속
- UE4
- Effective c++
- 스마트 포인터
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 8. nullptr 본문
728x90
항목 8. 0과 NULL보다 nullptr을 선호하라
0과 NULL
- 리터럴 0은 int이다.
- 포인터만 사용할 수 있는 위치에 0을 사용하면 암시적 변환으로 널 포인터로 해석하긴 하지만, 기본은 int.
- 즉, 포인터가 아니다.
- NULL 역시 마찬가지다.
- 하지만, NULL은 int 이외의 정수 타입(long)을 부여할 수도 있으므로 int라고 확신할 순 없다.
- 아무튼 NULL 역시 포인터 타입이 아니다.
C++98에서의 오버로딩 문제
void f(int);
void f(bool);
void f(void*);
f(0); // f(void*) 가 아닌 f(int)를 호출
f(NULL); // f(void*) 가 아닌 f(int)를 호출하거나 컴파일 에러
- 만일 NULL의 구현이 int가 아니라면 암시적 변환의 우선순위가 모두 같아서 컴파일 에러
- long -> int 변환
- long -> bool 변환
- 0L -> void* 변환
- NULL을 사용하면 외관상 의미와 실제 의미가 모순되므로
- C++98에서는 포인터 타입과 정수 타입을 오버로드 하는 걸 피하라는 지침을 따라왔다.
- C++11에서도 유효한 지침이긴 하다.
- 0과 NULL은 여전히 사용 가능하고, 그를 사용하는 개발자가 있을 것이기 때문이다.
nullptr
- 정수 타입이 아니며, 사실, 포인터 타입도 아니다.
- 모든 타입의 포인터라고 생각하면 된다.
- 실제 타입은 std::nullptr_t이다.
- std::nullptr_t는 다시 'nullptr의 타입'으로 정의된다.(순환 정의)
- std::nullptr_t는 모든 raw 포인터 타입으로 암묵적 변환된다.
- 위 예시에서는 반드시 f(void*) 버전이 선택된다.
- nullptr가 절대 정수 형식으로 해석되지 않기 때문이다.
- 또한 nullptr는 코드의 명확성을 높여준다.
auto result = findRecord( /* 인수들 */ );
if(result == 0) { ... }
// findRecord의 리턴 타입을 쉽게 파악할 수 없다면 코드 가독성이 나쁘다.
auto result = findRecord( /* 인수들 */ );
if(result == nullptr) { ... }
// 포인터 타입임이 명확해진다.
- 템플릿을 사용할 때도 명확한 사용이 가능하다.
템플릿과 using 예제
// 아래 함수들은 적절한 뮤텍스를 잠그고 호출해야 함!
int f1(std::shared_ptr<Widget> spw);
double f2(std::unique_ptr<Widget> upw);
bool f3(Widget* pw);
std::mutex f1m, f2m, f3m;
using MuxGuard = std::lock_guard<std::mutex>; // using은 항목 9 참조
{
MuxGuard g(f1m);
auto result = f1(0); // 0을 널포인터로 취급하여 f1에 전달. 성공.
}
{
MuxGuard g(f2m);
auto result = f2(NULL); // NULL을 널포인터로 취급하여 f1에 전달. 성공.
}
{
MuxGuard g(f3m);
auto result = f3(nullptr); // 성공.
}
- 위 코드의 코드 중복을 없애기 위해 템플릿을 도입한다.
// C++11 버전
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 버전
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); // 성공
- 이제 전달된 매개변수들은 템플릿 타입 추론이 적용된다.(항목 1 참조)
- 0의 타입은 항상 int로 추론된다.
- NULL의 타입은 항상 정수 타입으로 추론된다.
- nullptr의 타입은 항상 std::nullptr_t로 추론된다.
- 추론된 타입이 매개변수와 일치하지 않으면서 에러가 발생하게 된다.
728x90
'Effective C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 7. 괄호 초기화 vs 중괄호 초기화 (0) | 2025.02.13 |
---|---|
[Effective Modern C++] 6. auto의 타입 추론 실패 (0) | 2025.02.12 |
[Effective Modern C++] 5. 타입 명시보다 auto (0) | 2025.02.11 |
[Effective Modern C++] 4. 추론 타입 파악하기 (0) | 2025.02.10 |
[Effective Modern C++] 3. decltype (0) | 2025.02.07 |
Comments