icon안동민 개발노트

함수와 포인터 개념


 C++에서 함수와 포인터는 프로그램의 구조를 정의하고 메모리를 효율적으로 관리하는 데 중요한 역할을 합니다.

 언리얼 엔진에서는 이러한 개념들이 게임 로직 구현과 최적화에 핵심적으로 사용됩니다.

함수

 함수 선언과 정의

 함수는 특정 작업을 수행하는 코드 블록입니다. 언리얼 엔진에서 함수는 주로 클래스 내부에 정의됩니다.

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    // 함수 선언
    void Attack();
 
    // 함수 선언과 정의를 동시에
    void Heal(float Amount) { Health += Amount; }
 
private:
    float Health;
};
 
// 클래스 외부에서 함수 정의
void AMyCharacter::Attack()
{
    // 공격 로직 구현
}

 매개변수 전달 방식

  1. 값에 의한 전달
void SetHealth(float NewHealth) { Health = NewHealth; }
  1. 참조에 의한 전달
void ModifyHealth(float& HealthRef) { HealthRef += 10; }
  1. 포인터에 의한 전달
void DamageHealth(float* HealthPtr) { *HealthPtr -= 5; }

 UFUNCTION 매크로

 UFUNCTION 매크로는 언리얼 엔진의 리플렉션 시스템에 함수를 등록합니다.

 이를 통해 블루프린트에서 함수를 호출하거나, 네트워크 복제 등의 기능을 사용할 수 있습니다.

UFUNCTION(BlueprintCallable, Category = "Combat")
void PerformSpecialAttack(FString AttackName, float Damage);

 주요 UFUNCTION 지정자

  • BlueprintCallable : 블루프린트에서 호출 가능
  • BlueprintPure : 사이드 이펙트 없는 순수 함수
  • Server : 서버에서만 실행되는 함수
  • Client : 클라이언트에서 실행되는 함수
  • NetMulticast : 모든 클라이언트에서 실행되는 함수

포인터

 포인터는 메모리 주소를 저장하는 변수입니다.

 언리얼 엔진에서 포인터는 동적 객체 생성, 객체 간 참조 등에 사용됩니다.

 기본 포인터 사용

UCLASS()
class AWeapon : public AActor
{
    GENERATED_BODY()
 
public:
    void Fire();
};
 
UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
private:
    AWeapon* CurrentWeapon;
 
public:
    void EquipWeapon(AWeapon* NewWeapon)
    {
        CurrentWeapon = NewWeapon;
    }
 
    void UseWeapon()
    {
        if (CurrentWeapon)
        {
            CurrentWeapon->Fire();
        }
    }
};

 동적 메모리 할당

 C++에서는 new와 delete를 사용하여 동적으로 메모리를 할당하고 해제합니다.

 그러나 언리얼 엔진에서는 이러한 방식을 직접 사용하는 것을 권장하지 않습니다.

// 권장하지 않는 방식
AWeapon* Weapon = new AWeapon();
// 사용 후
delete Weapon;
 
// 언리얼 엔진에서 권장하는 방식
AWeapon* Weapon = GetWorld()->SpawnActor<AWeapon>(AWeapon::StaticClass());
// 사용 후
Weapon->Destroy();

 스마트 포인터

 언리얼 엔진은 메모리 관리를 돕기 위해 여러 스마트 포인터 타입을 제공합니다.

  1. TSharedPtr : 공유 소유권을 가진 포인터
TSharedPtr<FMyClass> SharedObject = MakeShared<FMyClass>();
  1. TWeakPtr : 약한 참조를 가진 포인터
TWeakPtr<FMyClass> WeakObject = SharedObject;
  1. TUniquePtr : 단일 소유권을 가진 포인터
TUniquePtr<FMyClass> UniqueObject = MakeUnique<FMyClass>();

 가비지 컬렉션과 상호작용

 언리얼 엔진의 가비지 컬렉션 시스템은 UPROPERTY로 표시된 UObject 포인터를 자동으로 관리합니다.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
 
    UPROPERTY()
    UStaticMeshComponent* MeshComponent;
 
    // 이 포인터는 가비지 컬렉션의 대상이 아님
    AnotherClass* NonTrackedPointer;
};

주의사항 및 성능 고려사항

  1. 댕글링 포인터 방지
  • 항상 포인터가 유효한 객체를 가리키고 있는지 확인하세요.
if (IsValid(SomeActor))
{
    SomeActor->DoSomething();
}
  1. 순환 참조 방지
  • TWeakPtr를 사용하여 순환 참조를 방지할 수 있습니다.
  1. 캐시 친화적 코드 작성
  • 포인터 체이싱을 최소화하고, 데이터를 연속적으로 배치하여 캐시 효율성을 높이세요.
  1. 가상 함수 사용 주의
  • 가상 함수는 편리하지만, 과도한 사용은 성능 저하를 일으킬 수 있습니다.
  1. 함수 인라이닝 고려
  • 자주 호출되는 작은 함수는 FORCEINLINE 키워드를 사용하여 인라인화를 고려하세요.
FORCEINLINE float GetHealth() const { return Health; }
  1. 불필요한 동적 할당 피하기
  • 가능한 한 스택 할당이나 오브젝트 풀링을 사용하여 빈번한 동적 할당을 피하세요.
  1. 스마트 포인터 오버헤드 인식
  • 스마트 포인터는 편리하지만, 레퍼런스 카운팅 등의 오버헤드가 있습니다. 성능이 중요한 경우 raw 포인터 사용을 고려하세요.
  1. UFUNCTION 사용 시 주의
  • UFUNCTION 매크로는 추가적인 오버헤드를 발생시킬 수 있습니다. 성능이 중요한 경우, 불필요한 사용을 피하세요.

 함수와 포인터는 C++ 프로그래밍의 근간을 이루는 개념이며, 언리얼 엔진에서도 핵심적으로 사용됩니다. 이들을 효과적으로 활용하면 깔끔하고 효율적인 게임 코드를 작성할 수 있습니다.

 그러나 동시에 메모리 관리와 성능 최적화에 주의를 기울여야 합니다. 언리얼 엔진의 특성을 이해하고, 제공되는 도구와 패턴을 적절히 활용함으로써, 안정적이고 고성능인 게임을 개발할 수 있습니다.