Log Stash

as an Industrial Personnel

Note

TIL: Unity compares Objects to null implicitly

SavvyTuna 2017. 6. 29. 01:15

묵시적 형 변환

C,C++에서는 어떤 변수가 NULL인지 아닌지 검사하기위해 아래와 같은 조건문을 쓸 수 있다.

Asdf* asdf = new Asdf;

if (asdf) {
    // do sth
}

delete asdf;

이 동네에선 조건문 안에 있는 수식을 평가해서 나온 값이 0이면 FALSE, 그게 아니면 TRUE로 판단하기 때문이다.

Java나 C#같은 고오급 언어에선 다르다. if 조건문 안에 bool 타입의 수식이 들어가야 한다. 그냥 C,C++에서 하던대로 쿨하게 if 조건 안에 변수 하나만 명시하는건 그 변수가 bool 타입이 아닌 이상 불가능하다.

Asdf asdf = new Asdf();

if (asdf == null)
{ // do sth } if (asdf)
{ // error: Cannot implicitly convert type 'Asdf' to 'bool' }

하지만 유니티에선 이게 가능하다. 물론 다 되는건 아니고 정확히는 유니티 엔진의 Object를 상속받는 클래스의 인스턴스들만 가능하다. (object 아님!) MonoBehaviourComponent 같이 Object를 상속받는 클래스들도 당연히 가능.

GameObject asdf;

if (asdf) 
{ // do sth }

왜 가능하냐면 유니티의 Object 클래스가 bool타입에 대해서 묵시적 형 변환 연산자를 구현해 놨기 때문.

// UnityEngine/Object.cs

public static implicit operator bool(Object exists)
{
    return !Object.CompareBaseObjects(exists, null);
}

private static bool CompareBaseObjects(Object lhs, Object rhs)
{
    bool flag = lhs == null;
    bool flag2 = rhs == null;
    bool result;
    if (flag2 && flag)
    {
        result = true;
    }
    else if (flag2)
    {
        result = !Object.IsNativeObjectAlive(lhs);
    }
    else if (flag)
    {
        result = !Object.IsNativeObjectAlive(rhs);
    }
    else
    {
        result = (lhs.m_InstanceID == rhs.m_InstanceID);
    }
    return result;
}

private static bool IsNativeObjectAlive(Object o)
{
    return o.GetCachedPtr() != IntPtr.Zero || (!(o is MonoBehaviour) && !(o is ScriptableObject) && Object.DoesObjectWithInstanceIDExist(o.GetInstanceID()));
}

Object null 체크

저게 왜 되는건지 찾아보다가 유니티의 null체크 방식들과 그 성능을 비교한 아티클을 우연히 먼저 찾았는데, 여기서 말하길 대부분의 유니티 사용자가 제일 많이 사용하는 (아마) null 체크 방식인 GameObject타입과 null 타입을 비교하는것’ 이 제일 느리고, ‘게임 오브젝트를 object형으로 다운 캐스팅 시키고 null과 비교하는 것’ 이 제일 빠른 성능을 보여준다는 결과를 보게 되었다.

GameObject obj;

if (obj == null) 
{ // slowest } if ((object)obj == null)
{ // fastest }

처음에 봤을땐 ‘nullGameObject 타입으로 승격시키는데 시간이 걸리나?’ 했는데, 위의 Object.cs의 코드를 보면 CompareBaseObjects()안에서 내부 변수들이 진짜로 살아있는지 이것저것 더 살펴본다. 여기에서 시간이 더 들어가는것일듯. 게임 오브젝트를 object로 강제 캐스팅해서 비교하는 로직에선 위의 각종 검사 루틴을 거치지 않고, 그냥 C# 레퍼런스가 null인지 아닌지 여부만 검사하고 끝내니까 더 빠를테고.

obj가 진짜로 null을 가리키는 레퍼런스 일 때도 true를 반환하게 하는것에 더해서, Destroy(obj)등으로 C++ 쪽 객체가 파괴되는경우 C#쪽 레퍼런스는 null이 아니라 무엇인가를 가리키고 있는 상태이지만, 실제로는 가리키는것이 파괴된(될) 객체인 경우에도 null체크에서 true를 반환하게 하기 위해서 이렇게 추가 로직을 더 구현 해 놓은듯 하다.

더 알아보고 싶으면 유니티 블로그의 아티클참조.

References