이 글은 해당 유튜브를 보고 진행한 것에 대해 공부하여 정리한 글입니다.
해당 강의에서는 전 강의에서 만들었던 데이터 테이블을 기반으로, 아이템 기반 클래스를 만들었습니다.
해당 클래스를 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 |