C++에서 nullptr은 C++11에서 도입된 포인터를 명확히 나타내는 키워드입니다.
이전에는 포인터를 초기화할 때 0 또는 NULL을 사용했지만,
nullptr은 "이 변수는 포인터이고, 아무것도 가리키지 않는다"라는 의미를 보다 명확하게 전달합니다.
이 문서에서는 nullptr의 필요성, 장점, 단점, 사용법을 정리하고,
예제 코드와 함께 자세히 분석하겠습니다.
#include <iostream>
using namespace std;
class Knight {
public:
void Test() { } // 단순한 멤버 함수
};
✅ 클래스 Knight
Test()라는 멤버 함수를 가집니다.Knight* FindKnight() {
return nullptr; // 아무 객체도 가리키지 않음을 명확하게 표현
}
✅ 포인터 반환 함수 FindKnight()
nullptr을 반환하여, 이 함수가 유효한 포인터를 반환하지 않음을 명확히 나타냅니다.0 또는 NULL을 반환했지만, nullptr은 포인터와 정수 간 혼동을 방지합니다.void Test(int a) {
cout << "Test(int)" << endl;
}
void Test(void* ptr) {
cout << "Test(*)" << endl;
}
✅ 오버로딩된 Test() 함수
Test(int): 정수를 매개변수로 받음.Test(void*): 포인터를 매개변수로 받음.nullptr을 사용하면 의도한 void* 버전이 호출됨._NullPtr (C++ 표준 nullptr 구현 예제)const class {
public:
template<typename T>
operator T* () const { return 0; }
template<typename C, typename T>
operator T C::* () const { return 0; }
private:
void operator&() const; // 주소값 사용을 방지
}_NullPtr;
✅ 사용자 정의 _NullPtr
operator T*)operator T C::*)로도 변환 가능& 사용을 방지하여 불필요한 사용을 막음nullptr 구현 방식과 유사한 기능 제공main() 함수 분석int main() {
int* ptr = nullptr;
✅ nullptr을 사용한 포인터 초기화
nullptr과 정수 구분 Test(0); // Test(int) 호출
Test(NULL); // Test(int) 호출
✅ 문제점 (0과 NULL)
0과 NULL은 모두 정수로 간주되므로 Test(int)가 호출됨.Test(void*)를 호출하려면 nullptr을 사용해야 함. Test(nullptr); // Test(*) 호출
✅ nullptr을 사용하면 void* 함수가 정확히 호출됨.
nullptr은 정수(int)가 아니라 void* 타입으로 자동 변환됨._NullPtr을 활용한 함수 호출 Test(_NullPtr); // Test(*) 호출
✅ 사용자 정의 _NullPtr을 사용하여 void* 버전 호출
_NullPtr은 operator T* 연산자를 통해 포인터로 변환되므로 Test(void*)가 호출됨.nullptr과 비교 auto Knight = FindKnight();
if (_NullPtr == Knight) {
cout << "Knight is null" << endl;
}
✅ nullptr과 _NullPtr을 활용한 비교
FindKnight()가 반환하는 값이 nullptr인지 확인하는 코드. auto whoami = _NullPtr;
✅ _NullPtr을 auto 타입으로 저장
_NullPtr을 적절한 포인터 타입으로 추론함. void(Knight::* memFunc)();
memFunc = &Knight::Test;
if (memFunc == _NullPtr) {
cout << "Member function pointer is null" << endl;
}
✅ 멤버 함수 포인터 초기화
memFunc는 Knight 클래스의 멤버 함수 포인터._NullPtr은 멤버 함수 포인터 타입(T C::*)으로 변환될 수 있어 비교 가능. return 0;
}
✅ main() 종료
nullptr이 필요한 이유void Test(int a);
void Test(void* ptr);
Test(0); // int 버전 호출됨
Test(NULL); // int 버전 호출됨
Test(nullptr); // void* 버전 호출됨 (정확한 호출)
0과 NULL은 정수로 간주되므로 Test(int)가 호출됨.nullptr은 void*로 변환되므로 올바른 오버로딩을 선택 가능.auto obj = FindKnight();
if (obj == nullptr) {
// obj가 포인터인지 명확함
}
nullptr은 포인터임을 명확히 나타내므로 가독성이 향상됨.nullptr을 사용자 정의 타입으로 구현 가능class NULLptr {
public:
template<typename T>
operator T* () const { return 0; }
template<typename C, typename T>
operator T C::* () const { return 0; }
void operator&() const = delete; // 주소값 유출 방지
};
NULL이 0으로 정의되어 있어 정수와 혼동되던 문제를 해결할 수 있음.