UE5

[UE5] FORCEINLINE, Overloading, Clamp

sunlight-dby 2025. 5. 2. 00:46

 

이 글은 해당 유튜브를 보고 진행한 것에 대해 공부하여 정리한 글입니다.

 

 


 

해당 강의에서는 전 강의에서 만들었던 데이터 테이블을 기반으로, 아이템 기반 클래스를 만들었습니다.

해당 클래스를 C++로 생성하여, 필요한 프로퍼티와 함수들을 생성하였습니다.

 

함수를 생성할 때 쓰인 것들에 대해 먼저 정리하고자 합니다.


 

C++ : inline

언리얼에서 지원하는 Forceinline을 정리하기 전, C++ 표준의 inline에 대해 먼저 정리하고자 합니다.

 

C++의 inline은 함수를 정의할 때 사용하는 키워드입니다.

컴파일러에게 호출 코드를 함수 본체의 코드로 대체(인라인화)하도록 요청하는 힌트 역할을 합니다.

 

[목적]

함수 호출 오버헤드를 줄여 프로그램의 성능을 향상시키는 것을 목표로 합니다.

함수 호출에는 스택에 인자를 푸시하고, 함수 주소로 점프하고, 스택에서 인자를 팝하는 등의 오버헤드가 발생하는데 inline 함수를 사용하면, 이를 줄일 수 있습니다.

 

[원리]

inline 함수는 컴파일러에게 함수 본체의 코드를 호출되는 위치에 직접 삽입하도록 요청합니다.

이렇게 되면 함수 호출 과정 자체가 사라지게 됩니다.

 

또한 아래의 과정들이 이루어집니다.

  • 인자를 스택에 푸시하거나 레지스터에 저장할 필요 없이, 변수가 직접 사용됩니다.
  • 리턴 주소를 스택에 저장하고 함수 시작 주소로 점프하는 과정이 없어집니다. 실행 흐름이 그대로 이어지게 됩니다.
  • 지역 변수 할당 / 해제 과정이 단순화되거나 사라집니다.

이러한 과정들 덕분에 오버헤드가 제거되면서 함수 호출에 드는 시간이 절약됩니다.

 

[예시]

// 인라인 함수
inline int add(int a, int b)
{
    return a + b;
}

// 호출
int result = add(5,3);

 

컴파일러가 add 함수를 성공적으로 인라인화하면, 위 코드는 컴파일 후에 아래의 코드로 변환될 수 있습니다.

// 인라인화 후
int result = 5 + 3;  // 함수 호출 없이 직접 코드 삽입

 

[주의할 점]

  • 컴파일러는 inline 키워드를 보고 해당 함수를 인라인화할지 여부를 결정합니다.
    inline은 권장사항일 뿐, 컴파일러가 반드시 따르는 것은 아닙니다.
  • 컴파일러는 함수의 크기, 호출 빈도, 최적화 설정 등 다양한 요소를 고려하여 인라인화 여부를 결정합니다.
    함수가 너무 크거나 복잡하면 인라인화하지 않을 수 있습니다.
  • inline으로 정의된 함수는 일반적으로 헤더 파일에 정의되어야 합니다.
    이는 컴파일러가 각 번역 단위에서 함수 본체를 볼 수 있도록 하기 위함입니다.
  • 과도한 인라인화는 코드를 증가시켜 캐시 효율성을 떨어뜨리고 오히려 성능 저하를 유발할 수 있습니다.
  • 재귀 함수는 인라인화될 수 없습니다. 컴파일러가 재귀를 감지하여 인라인화를 회피합니다.

UE : FORCEINLINE

FORCEINLINE은 언리얼 엔진에서 제공하는 매크로로, C++ 표준의 inline보다 더 강력하게 컴파일러에게 함수를 인라인화하도록 강제하는 역할을 합니다.

 

[목적]

C++의 inline과 동일하게 오버헤드를 최소화하여 최대 성능을 확보하고자 할 때 사용합니다.

 

[원리]

언리얼에서 FORCEINLINE은 컴파일러 특정 지시어로 확장됩니다.

이 지시어는 inline 키워드보다 더 높은 우선 순위를 가지며, 컴파일러에게 해당 함수를 인라인화하도록 강제합니다.

※ 컴파일러 특정 지시어 : __forceinline in MSVC, always_inline attribute in Clang/GCC

 

컴파일러에게 인라인화하도록 강제한다고 해서 반드시 인라인화하는 것은 아닙니다.

너무 큰 함수나 가상 함수, 재귀 함수가 사용되는 등의 인라인화가 불가능한 특정 상황에서는 강제되더라도 인라인화되지 않을 수 있습니다.

 

[주의할 점]

  • FORCEINLINE은 inline보다 코드 크기 증가의 위험이 더 높습니다. 그렇기에 반드시 필요한 경우에만 사용해야 합니다.
    • 코드가 어느정도 길더라도 강제로 인라인화하기 때문에 그렇습니다.
  • 디버깅 시 인라인화된 함수는 스택 추적에서 나타나지 않을 수 있으니 주의해야 합니다.

오버로딩 (OVERLOADING)

오버로딩은 다들 아시겠지만, 하나의 이름으로 여러 개의 함수 또는 연산자를 정의하는 기능입니다.

해당 강의에서는 == 연산자를 오버로딩하여 아이템의 ID를 비교하는 연산자를 만들었습니다.

bool operator==(const FName& OtherID) const
{
	return ID == OtherID;
}

 

오버로딩을 위해 operator 키워드를 사용하였습니다.

 

[operator]

C++의 operator 키워드는 연산자 오버로딩을 수행할 때 사용됩니다.

사용자 정의 타입에 대해 기본 제공 타입처럼 자연스럽게 연산자를 사용할 수 있도록 하는 것입니다.

 

[작동방식]

operator 키워드 뒤에 오버로딩하려는 연산자 기호를 씁니다.

이 오버로딩된 연산자는 함수처럼 정의되며, 해당 연산자가 피연산자로 받는 객체에 대한 동작을 구현합니다.


C++ : Clamp

이번에도 UE에서의 Clamp를 보기 전, C++의 Clamp를 먼저 살펴보겠습니다.

 

C++ 표준 std::clamp

[헤더파일]

  • <algorithm>

[정의]

  • 특정 값을 주어진 최솟값과 최댓값 범위 내로 제한하는 데 사용됩니다.

[함수 원형]

template <class T>
constexpr const T& clamp(const T& value, const T& minValue, const T& maxValue);

 

value가 minValue보다 작으면 minValue를 반환하고, maxValue보다 크면 maxValue를 반환합니다.

value가 minValue와 maxValue 사이에 있으면 그대로 value를 반환합니다.

 

[주의할 점]

  • 최소값이 최대값보다 작거나 같아야 합니다. 이 조건을 만족하지 않으면 동작이 정의되지 않습니다.

UE : Clamp

언리얼 엔진에서 지원하는 Clamp는 FMath::Clamp로 사용됩니다.

 

FMath::Clamp

[헤더파일]

  • Math/UnrealMathUtility.h
  • 보통은 자동 포함됩니다.

[정의]

  • C++의 std::Clamp와 동일하게, 특정 값을 주어진 최솟값과 최댓값 범위 내로 제한하는 데 사용됩니다.

[함수 원형]

template< class T >
static FORCEINLINE T Clamp( const T V, const T Min, const T Max )
{
    return V < Min ? Min : (V < Max ? V : Max);
}

 

[예제]

void UItemBase::SetQuantity(const int32 NewQuantity)
{
    if (NewQuantity != Quantity)
    {
        Quantity = FMath::Clamp(NewQuantity, 0, NumericData.bIsStackable ? NumericData.MaxStackSize : 1);
    }
}

실습

[ItemBase.h]

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "ItemDataStructs.h"
#include "InventorySystemCharacter.h"
#include "ItemBase.generated.h"

/**
 *
 */
UCLASS()
class INVENTORYSYSTEM_API UItemBase : public UObject
{
	GENERATED_BODY()

public:
	// PROPERTIES & VARIABLESUPROPERTY(VisibleAnywhere, Category = "Item Data", meta = (UIMin = 1, UIMax = 100))
	int32 Quantity;

	UPROPERTY(EditAnywhere, Category = "Item Data")
	FName ID;

	UPROPERTY(EditAnywhere, Category = "Item Data")
	EItemType ItemType;

	UPROPERTY(EditAnywhere, Category = "Item Data")
	EItemQuality ItemQuality;

	UPROPERTY(EditAnywhere, Category = "Item Data")
	FItemStatistics ItemStatistics;

	UPROPERTY(EditAnywhere, Category = "Item Data")
	FItemTextData TextData;

	UPROPERTY(EditAnywhere, Category = "Item Data")
	FItemNumericData NumericData;

	UPROPERTY(EditAnywhere, Category = "Item Data")
	FItemAssetData ItemAssetData;

	// FUNCTIONS
	UItemBase();

	UItemBase* CreateItemCopy() const;

	UFUNCTION(Category = "Item")
	FORCEINLINE float GetItemStackWeight() const { return Quantity * NumericData.Weight; };

	UFUNCTION(Category = "Item")
	FORCEINLINE float GetItemSingleWeight() const { return NumericData.Weight; };

	UFUNCTION(Category = "Item")
	FORCEINLINE bool IsFullItemStack() const { return Quantity == NumericData.MaxStackSize; };

	UFUNCTION(Category = "Item")
	void SetQuantity(const int32 NewQuantity);

	UFUNCTION(Category = "Item")
	virtual void Use(AInventorySystemCharacter* Character);

protected:
	// OVERLODING OPERATOR
	bool operator==(const FName& OtherID) const
	{
		return ID == OtherID;
	}
};

[ItemBase.cpp]

// Fill out your copyright notice in the Description page of Project Settings.


#include "ItemBase.h"

UItemBase::UItemBase()
{
	// 항목은 데이터 테이블로 초기화할 것이기에 생성자 필요 X
}

UItemBase* UItemBase::CreateItemCopy() const
{
	UItemBase* ItemCopy = NewObject<UItemBase>(StaticClass());

	ItemCopy->ID = this->ID;
	ItemCopy->Quantity = this->Quantity;
	ItemCopy->ItemQuality = this->ItemQuality;
	ItemCopy->ItemType = this->ItemType;
	ItemCopy->TextData = this->TextData;
	ItemCopy->NumericData = this->NumericData;
	ItemCopy->ItemStatistics = this->ItemStatistics;
	ItemCopy->ItemAssetData = this->ItemAssetData;

	return ItemCopy;
}

void UItemBase::SetQuantity(const int32 NewQuantity)
{
	if (NewQuantity != Quantity)
	{
		Quantity = FMath::Clamp(NewQuantity, 0, NumericData.bIsStackable ? NumericData.MaxStackSize : 1);
	}
}

void UItemBase::Use(AInventorySystemCharacter* Character)
{
}

 

'UE5' 카테고리의 다른 글

[UE5] Enum & Struct  (0) 2025.04.13
[UE5] Git 사용하기  (0) 2025.04.06