ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Unreal] 언리얼 유효성 검사 - IsValid, == nullptr, ensure
    쾌락없는 책임 (공부)/Unreal 2023. 5. 15. 22:54
    반응형

    개요

    평소 언리얼에서 포인터 타입들에 대해 유효성 검사를 할 때 if(Ptr == nullptr) return; 이런 식으로 사용했습니다. 그런데 이와 관련한 여러 함수들이 있는 것을 점차 발견하게 되었습니다. 그래서 한번 차이를 알아보고 어떤 차이점이 있는지 알아보려고 합니다.

     

     

    IsValid()

    이 함수가 유효성 검사에서 주로 사용되는 함수입니다.

    FORCEINLINE bool IsValid(const UObject *Test)
    {
    	return Test && FInternalUObjectBaseUtilityIsValidFlagsChecker::CheckObjectValidBasedOnItsFlags(Test);
    }

    자세히 보면 Test 포인터가 nullptr임을 판단함과 동시에 무언가 CheckObjectValidBasedOnItsFlags 함수를 호출하는 모습을 볼 수 있습니다. 이 함수의 본문을 보면

    struct FInternalUObjectBaseUtilityIsValidFlagsChecker
    {
    	FORCEINLINE static bool CheckObjectValidBasedOnItsFlags(const UObject* Test)
    	{
    		// Here we don't really check if the flags match but if the end result is the same
    		PRAGMA_DISABLE_DEPRECATION_WARNINGS
    		checkSlow(GUObjectArray.IndexToObject(Test->InternalIndex)->HasAnyFlags(EInternalObjectFlags::PendingKill | EInternalObjectFlags::Garbage) == Test->HasAnyFlags(RF_InternalPendingKill | RF_InternalGarbage));
    		PRAGMA_ENABLE_DEPRECATION_WARNINGS
    		return !Test->HasAnyFlags(RF_InternalPendingKill | RF_InternalGarbage);
    	}
    };

    이런 식으로 되어 있습니다. 여러 매크로들이 있어서 본격적인 구현사항을 보기는 어려웠지만 내부에 플래그를 보는 것으로 보입니다. PendingKill인지 아니면 Garbage 인지를 체크 하는 것으로 보입니다. 실제로 언리얼의 공식 독스를 보면

    Return true if the object is usable: non-null and not pending kill or garbage

    라고 되어 있는걸 볼 수 있습니다. 결국 nullptr인지 pending kill인지 garbage인지, 총 3가지를 체크하는 것입니다.

     

     

    Pending Kill은 무엇일까?

    garbage는 GC에 수집될 것이라는 걸 알겠고, Pending Kill이 어떤 것인지 잘 모르고 있었습니다. 알고 보니 이는 곧 파괴될 오브젝트를 의미합니다.

    오브젝트가 Destroy 된다고 하면 바로 뿅! 하고 사라지는게 아니라 실제로는 Pending Kill 상태가 되고 이후 삭제가 될 것이라는 것이죠. 즉, 메모리에는 남아 있지만 곧이어 삭제될 오브젝트를 나타내는 플래그라고 볼 수 있습니다. 그리고 이 와중 접근하게 된다면 언제 객체가 사라질지 모르니 체크를 해 주는 것이죠.

     

     

    RF_InternalGarbage는 무엇인가

    이건 위 설명에도 있지만 딱봐도 garbage가 된 것을 의미하는 것으로 보입니다. GC에서 처리해야 할 객체들을 의미하는 것으로 보이는데

    RF_Garbage UE_DEPRECATED(5.0, "RF_Garbage should not be used directly. Use MarkAsGarbage and ClearGarbage instead.") =0x40000000,	
    ///< Garbage from logical point of view and should not be referenced. 
    ///This flag is mirrored in EInternalObjectFlags as Garbage for performance

    실제로는 RF_Garbage를 가리키는 역할을 합니다. 

     

    이게 현재 GC의 가비지인지 앞으로 처리할 가비지인지는 잘 모르겠네요.

     

     

    ensure는 뭘까

    지금까지 IsValid가 어떤 것인지 알아봤고 이제 ensure를 알아볼 것입니다. 이 구문은 언리얼의 Assert중 하나입니다. Assert, 검증한다는 코드입니다. 포인터가 null인지 등의 여러 검증이 가능한 것들입니다.

     

    표현식을 검증하여 실패하면 그 지점까지 이르는 콜스택을 생성합니다.

    위 설명이 ensure에 대한 설명입니다. 만일 아래처럼 쓴다면

    TObjectPtr<UObject> Ptr;
    //...
    ensure(Ptr);

    Ptr의 operator bool() 함수가 불리게 되겠죠. 만일 그냥 raw pointer 라면 =! nullptr과 같은 역할을 하게 될 것입니다.

     

     

    그러면 TObjectPtr에서 operator bool()

    5.0 이상을 보면 TObjectPtr을 통해 멤버 변수들을 정의하고 있을 겁니다. 추후 TObjectPtr이 점차 확대될 것이니 TObjectPtr과 nullptr 체크를 한번 알아두면 나쁠 건 없어 보입니다. 일단 nullptr과 비교를 한번 보시죠.

    bool operator==(TYPE_OF_NULLPTR) const
    {
        return !ObjectPtr.operator bool();
    }
    
    bool operator!=(TYPE_OF_NULLPTR) const
    {
        return ObjectPtr.operator bool();
    }

    일단 nullptr과 호환되는 비교 연산은 위와 같습니다.보시면 operator bool()을 호출하는걸 계속 볼 수 있죠.

    explicit FORCEINLINE operator bool() const { return !IsObjectHandleNull(Handle); }
    
    inline bool IsObjectHandleNull(FObjectHandle Handle) { return !Handle.PointerOrRef; }

    operator bool()은 이런 모습인데 다시 IsObjectHandleNull을 보는 걸 알 수 있습니다. 이름만 봐서는 이게 null을 다루고 있는지에 대한 여부를 알려주는 것으로 보이네요.

     

    여기서 Handle이 FObjectPtr에 있는 멤버 변수인데 

    FObjectPtr& operator=(TYPE_OF_NULLPTR)
    {
        Handle = MakeObjectHandle(nullptr);
        return *this;
    }

    이런 식으로 되는 것을 보니 nullptr을 대입할 때 Handle도 nullptr 되는 것으로 보입니다.

     

    다만 Handle을 통한 TObjectPtr의 operator bool()이 IsValid와 동일한 역할을 하는지에 대해서는 알지 못했습니다. 누군가의 제보를 바라겠습니다...ㅠㅠ

     

     

    정리

    정리를 해보면 아래와 같습니다.

    • IsValid() : nullptr 체크 외에도 pending kill 상태인지, garbage 인지 체크해 줍니다.
    • ensure() : Assert 함수중 하나로 ensure(pointer)를 할 경우 포인터의 operator bool() 연산에 의존하게 됩니다.

     

     

    참고 자료

     

    IsValid

    Test validity of object

    docs.unrealengine.com

     

    어서트

     

    docs.unrealengine.com

     

    반응형

    댓글

Designed by Tistory.