본문 바로가기

CDO (Class Default Object)

@iamrain2025. 10. 21. 11:56

CDO(Class Default Object)는 언리얼 엔진의 UObject 시스템에서 각 UClass에 대해 생성되는 특별한 '템플릿' 또는 '원형(Archetype)' 객체로 클래스가 로드될 때, 엔진은 해당 클래스의 기본 속성 값을 가지는 인스턴스를 하나 생성하는데, 이것이 바로 CDO다.

이 CDO는 이후 해당 클래스의 모든 인스턴스를 생성할 때 기본 값의 원본으로 사용된다. 즉, NewObject<T>()GetWorld()->SpawnActor<T>()를 통해 새로운 객체를 생성하면, 이 객체의 초기 속성 값은 CDO의 속성 값을 그대로 복사하여 설정된다.

1. 주요 특징

  • 템플릿 역할: CDO는 클래스의 '청사진' 역할을 한다. 클래스의 모든 인스턴스는 CDO를 기반으로 생성된다.
  • 기본값 저장소: 클래스 생성자에서 설정된 기본값, 블루프린트 클래스에서 설정된 기본값들이 CDO에 저장된다.
  • 메모리 효율성: 모든 인스턴스가 각자의 기본값을 저장하는 대신, CDO라는 공유되는 하나의 객체에 기본값을 저장함으로써 메모리를 절약한다. 값이 변경되지 않은 속성은 인스턴스에 별도의 메모리를 할당하지 않고 CDO의 값을 참조할 수 있다.
  • 자동 생성 및 관리: 개발자가 직접 CDO를 생성하거나 파괴하지 않는다. 언리얼 엔진의 UObject 시스템이 클래스가 메모리에 로드될 때 자동으로 생성하고, 클래스가 언로드될 때 파괴한다.

2. CDO의 내부 구조와 구현 방식

CDO의 핵심은 UClass 내부에 구현되어 있다. 모든 UClass는 자신의 CDO를 가리키는 포인터를 가지고 있다.

UClass 내의 CDO 관련 멤버

UClass의 헤더 파일(Engine\Source\Runtime\CoreUObject\Public\UObject\Class.h)을 살펴보면 다음과 같은 멤버를 찾을 수 있다.

// Class Default Object
UObject* ClassDefaultObject;

ClassDefaultObject 포인터가 바로 해당 클래스의 CDO를 가리킨다. 이 포인터는 UClass::GetDefaultObject() 함수를 통해 외부에 노출된다.

// UClass::GetDefaultObject() 구현 예시
FORCEINLINE UObject* UClass::GetDefaultObject(bool bNoCreate) const
{
    // ... 여러 검증 과정 ...
    if (!ClassDefaultObject && !bNoCreate)
    {
        // CDO가 아직 생성되지 않았다면 생성합니다.
        const_cast<UClass*>(this)->CreateDefaultObject();
    }
    return ClassDefaultObject;
}

CDO 생성 과정 (UClass::CreateDefaultObject)

  1. 클래스 링크(Link): 클래스가 처음 사용되기 위해 메모리에 로드되면, 엔진은 UClass::Link 함수를 호출하여 클래스를 초기화한다.
  2. CDO 생성: Link 과정의 일부로 CreateDefaultObject()가 호출된다.
  3. 메모리 할당: StaticAllocateObject 함수를 사용하여 CDO를 위한 메모리를 할당한다. 이때 CDO는 다른 UObject 인스턴스와 구별하기 위해 EObjectFlags::RF_ClassDefaultObject 플래그를 가진다.
  4. 생성자 호출: 할당된 메모리 위에서 해당 클래스의 C++ 생성자가 호출된다. 이 시점에서 개발자가 코드에 정의한 기본값들이 CDO의 속성에 설정된다.
  5. 블루프린트 기본값 적용: 만약 해당 클래스가 블루프린트 클래스라면, C++ 생성자 호출 이후 블루프린트 에디터에서 설정한 기본값들이 CDO 위에 덮어씌워진다. 이 때문에 블루프린트에서 설정한 값이 C++ 코드의 생성자 값보다 우선순위를 가진다.

객체 생성 시 CDO의 역할

NewObjectSpawnActor가 호출될 때 내부적으로 StaticConstructObject_Internal 함수가 사용된다. 이 함수의 동작 과정은 다음과 같다.

  1. 템플릿 객체 결정: NewObject 호출 시 템플릿(기준)이 될 객체를 지정할 수 있다. 만약 템플릿이 명시적으로 지정되지 않으면, 해당 클래스의 CDO가 템플릿으로 사용된다.
  2. 메모리 할당: 새로운 객체를 위한 메모리를 할당한다.
  3. 속성 복사: UObject::InitProperties 함수가 호출되어 템플릿 객체(주로 CDO)의 속성 값을 새로 할당된 메모리로 bitwise 복사한다. (CDO가 '원형'으로 동작하는 핵심 원리)
  4. 생성자 재호출 (선택적): C++ 생성자는 이미 CDO를 만들 때 호출되었으므로, 일반적인 인스턴스를 만들 때는 다시 호출되지 않는 것이 기본 동작이다. 대신, FObjectInitializer를 사용하는 특정 생성자 오버로드를 통해 인스턴스별 추가 초기화가 가능하다.

3. CDO와 다른 개념 비교

특징 CDO (Unreal Engine) C++ 기본 생성자 Unity Prefabs
목적 클래스의 기본 속성 값 저장 및 인스턴스 생성의 원형 객체 초기화 재사용 가능한 게임 오브젝트의 '템플릿'
실체 클래스당 하나씩 존재하는 실제 '객체' 인스턴스 객체가 생성될 때마다 호출되는 '함수' 에셋 파일(.prefab)로 저장되는 데이터 집합
생성 시점 클래스가 메모리에 로드될 때 new 키워드 등으로 객체가 생성될 때 에디터에서 생성, 런타임에 인스턴스화
값 상속 Bitwise 속성 복사를 통해 이루어짐 생성자 코드 내에서 멤버 변수를 초기화 Prefab 인스턴스는 원본 Prefab의 값을 상속받고, 변경 사항을 override할 수 있음
런타임 수정 가능하지만 매우 위험. 수정 시 새로 생성되는 모든 인스턴스에 영향. 기존 인스턴스는 영향 없음. 불가능 (함수이므로) 원본 Prefab을 수정하면 모든 인스턴스에 반영 가능 (선택적)
장점 - 메모리 효율성
- 빠른 인스턴스 생성 (속성 복사)
- 블루프린트와의 강력한 통합
- C++ 표준
- 유연한 초기화 로직 구현
- 시각적 편집 용이
- 컴포넌트 기반의 유연한 구조
단점 - 런타임 수정 시 예측 어려운 부작용
- 개념이 생소할 수 있음
- 모든 인스턴스가 독립적인 메모리 차지
- 런타임에 기본값을 일괄 변경하기 어려움
- 텍스트 기반 데이터가 아니어서 병합/관리가 어려울 수 있음

언제 CDO를 활용하는 것이 좋은가?

  • 공통 기본값을 가진 객체를 대량으로 생성할 때: 예를 들어, 게임 월드에 수백 개의 동일한 스펙을 가진 AEnemy 액터를 스폰할 때, CDO는 매우 효율적인 생성 메커니즘을 제공한다.
  • 블루프린트를 통해 디자이너가 클래스의 기본값을 수정하게 하고 싶을 때: 디자이너가 블루프린트 클래스에서 수정한 값은 CDO에 저장되며, 이는 C++ 코드 변경 없이 게임 밸런싱 등을 가능하게 한다.
  • 특정 클래스의 기본값을 런타임에 조회하고 싶을 때: GetClass()->GetDefaultObject<T>()를 통해 언제든지 클래스의 기본값을 안전하게 읽어올 수 있다.

4. CDO의 계층 구조적 동작 방식

  1. 사용자 영역 (C++ 코드 / 블루프린트)
    • 개발자는 C++ 클래스의 생성자에서 멤버 변수의 기본값을 설정함.
    • AMyActor::AMyActor() { PrimaryActorTick.bCanEverTick = true; Damage = 10.0f; }
    • 레벨 디자이너는 이 C++ 클래스를 기반으로 블루프린트 클래스(BP_MyActor)를 만들고, 디테일 패널에서 Damage 값을 20.0f로 변경함.
  2. 언리얼 엔진 - 클래스 로딩 계층
    • 엔진이 시작되거나 BP_MyActor가 처음 필요해질 때, UClass 객체가 메모리에 로드됨.
    • 엔진은 BP_MyActorUClass에 대한 CDO를 생성함.
    • 먼저 AMyActor의 C++ 생성자가 호출되어 CDO의 Damage가 10.0f로 설정됨.
    • 그 다음, 블루프린트에서 변경된 사항(Damage = 20.0f)이 CDO 위에 덮어씌워짐. 최종적으로 BP_MyActor의 CDO는 Damage 값으로 20.0f를 가짐.
  3. 언리얼 엔진 - 객체 생성 계층
    • 개발자가 GetWorld()->SpawnActor<ABP_MyActor>(...)를 호출함.
    • 내부적으로 NewObject와 유사한 프로세스가 진행됨.
    • ABP_MyActor 클래스의 CDO를 템플릿으로 사용함.
    • 새로운 ABP_MyActor 인스턴스를 위한 메모리를 할당함.
    • CDO의 속성들(예: Damage = 20.0f)을 새로운 인스턴스로 메모리 복사함.
    • BeginPlay() 등 액터의 생명주기 함수가 호출됨.
  4. OS / 하드웨어 계층
    • 이 모든 과정은 최종적으로 OS가 관리하는 프로세스의 가상 메모리 공간에서 일어남. CDO와 각 인스턴스는 모두 힙(heap) 메모리에 할당된 UObject임.

5. 요약

 CDO는 Class Default Object의 약자로, 언리얼 엔진에서 클래스마다 하나씩 생성되는 '기본값 객체' 또는 '원형'이라고 할 수 있습니다. 클래스가 메모리에 로드될 때 엔진이 자동으로 생성해주며, 해당 클래스의 C++ 생성자나 블루프린트에서 설정한 모든 기본 프로퍼티 값을 가지고 있습니다.

 CDO를 사용하는 가장 큰 이유는 효율성입니다. 첫째, 새로운 객체를 생성할 때마다 생성자 코드를 실행하는 대신, CDO의 프로퍼티를 그대로 메모리 복사해오기 때문에 인스턴스 생성이 매우 빠릅니다. 둘째, 모든 인스턴스가 동일한 기본값을 각자 저장할 필요 없이 CDO 하나만 참조하면 되므로 메모리를 절약할 수 있습니다.

 또한 CDO는 블루프린트 시스템과 긴밀하게 연동되어, 프로그래머가 C++로 기본 구조를 만들면 디자이너가 블루프린트에서 그 값을 쉽게 수정하고 게임에 바로 적용할 수 있도록 해주는 핵심적인 역할을 담당합니다.

iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차