전체 코드

🚀 1. 개요

C++에서 nullptrC++11에서 도입된 포인터를 명확히 나타내는 키워드입니다.
이전에는 포인터를 초기화할 때 0 또는 NULL을 사용했지만,
nullptr"이 변수는 포인터이고, 아무것도 가리키지 않는다"라는 의미를 보다 명확하게 전달합니다.

이 문서에서는 nullptr필요성, 장점, 단점, 사용법을 정리하고,
예제 코드와 함께 자세히 분석하겠습니다.


📂 2. 코드 분석

🔹 클래스 정의

#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) 호출

문제점 (0NULL)

  • 0NULL은 모두 정수로 간주되므로 Test(int)가 호출됨.
  • Test(void*)를 호출하려면 nullptr을 사용해야 함.
    Test(nullptr); // Test(*) 호출

nullptr을 사용하면 void* 함수가 정확히 호출됨.

  • nullptr은 정수(int)가 아니라 void* 타입으로 자동 변환됨.

🔹 사용자 정의 _NullPtr을 활용한 함수 호출

    Test(_NullPtr); // Test(*) 호출

사용자 정의 _NullPtr을 사용하여 void* 버전 호출

  • _NullPtroperator T* 연산자를 통해 포인터로 변환되므로 Test(void*)가 호출됨.

🔹 nullptr과 비교

    auto Knight = FindKnight();
    if (_NullPtr == Knight) {
        cout << "Knight is null" << endl;
    }

nullptr_NullPtr을 활용한 비교

  • FindKnight()가 반환하는 값이 nullptr인지 확인하는 코드.
    auto whoami = _NullPtr;

_NullPtrauto 타입으로 저장

  • 컴파일러가 _NullPtr을 적절한 포인터 타입으로 추론함.

🔹 멤버 함수 포인터와 비교

    void(Knight::* memFunc)();
    memFunc = &Knight::Test;

    if (memFunc == _NullPtr) {
        cout << "Member function pointer is null" << endl;
    }

멤버 함수 포인터 초기화

  • memFuncKnight 클래스의 멤버 함수 포인터.
  • _NullPtr은 멤버 함수 포인터 타입(T C::*)으로 변환될 수 있어 비교 가능.

🔹 프로그램 종료

    return 0;
}

main() 종료

  • 프로그램이 성공적으로 종료됨.

📌 3. nullptr이 필요한 이유

🔹 1) 함수 오버로딩 해결

void Test(int a);
void Test(void* ptr);

Test(0);    // int 버전 호출됨
Test(NULL); // int 버전 호출됨
Test(nullptr); // void* 버전 호출됨 (정확한 호출)
  • 0NULL은 정수로 간주되므로 Test(int)가 호출됨.
  • nullptrvoid*로 변환되므로 올바른 오버로딩을 선택 가능.

🔹 2) 가독성 향상

auto obj = FindKnight();
if (obj == nullptr) { 
    // obj가 포인터인지 명확함
}
  • nullptr은 포인터임을 명확히 나타내므로 가독성이 향상됨.

🔹 3) 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; // 주소값 유출 방지
};
  • 기존의 NULL0으로 정의되어 있어 정수와 혼동되던 문제를 해결할 수 있음.

profile
李家네_공부방

0개의 댓글