Arknights EP - [Confront]
Categories
Tags
1790 words
9 minutes
[UE5/Part1] 2. UE C++ 표준 Docs
“이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해” 학습 내용을 정리한 강의 노트입니다.
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.이번 노트는 언리얼 표준 컨벤션을 요약 정리한 내용입니다.
강의 목표
- 프로그래밍을 시작하기 전에 알아두어야 할 언리얼 C++ 코딩 표준 이해
- 언리얼 C++ 코딩 표준에서 주의할 점 확인
구글 & 언리얼 C++ 코딩 표준 URL
클래스 체계
public 구현을 먼저 선언한 후 클래스의 private 구현이 뒤따라야 한다.
명명 규칙
- 파스칼 케이싱 (Pascal Casing) 사용
- U A S E b F 등의 접두어 활용
UObjectUAActorASwidgetS: Slate 라는 UI전용 클래스enumEboolb: 소문자 - ex:bPendingDestrction
- 그 외 대부분의 클래스는 접두사
F
포터블 C++ 코드 (ex: int)
int및 부호 없는int타입은 플랫폼에 따라 크기가 다를 수 있다.- 주요 타입은 아래와 같이 크기를 표기해준다.
TCHAR- character(문자) (TCHAR 크기 추정 금지)uint8- unsigned byte(부호 없는 바이트) (1바이트)int8- signed byte(부호 있는 바이트) (1바이트)uint16- unsigned shorts(부호 없는 short) (2바이트)int16- signed short(부호 있는 short) (2바이트)uint32- unsigned int(부호 없는 int) (4바이트)int32- signed int(부호 있는 int) (4바이트)uint64- unsigned quad word(부호 없는 쿼드 단어) (8바이트)int64- signed quad word(부호 있는 쿼드 단어) (8바이트)PTRINT- 포인터를 가질 수 있는 정수(PTRINT 크기 추정 금지)
Const 정확도
const가능하면 붙여주기- 그 외에 아래와 같은 경우에도
const활용
1. 루프
TArray<FString> StringArray;
for (const FString& : StringArray)
{
// 이 루프의 바디는 StringArray를 수정하지 않습니다.
}2. 포인터 자체에 const
// 데이터 수정은 가능하나, 포인터에 증감 연산자 사용은 불가함
T* const Ptr = ...;
// X 틀림
T& const Ref = ...;3. 데이터에 const
// X [복사] 나쁜 예 - const 배열 반환
const TArray<FString> GetSomeArray();
// [읽기] 좋은 예 - const 배열로의 레퍼런스 반환
const TArray<FString>& GetSomeArray();
// [읽기] 좋은 예- const 배열로의 포인터 반환
const TArray<FString>* GetSomeArray();
// X [나쁜 예 - const 배열로의 const 포인터 반환 (포인터/데이터 모두 const)
const TArray<FString>* const GetSomeArray();예시 포맷
규칙에 맞춰 코드를 작성하면, JavaDoc기반으로 자동 문서화 가능
최신 C++ 문법
- 언리얼 엔진은 기본적으로 C++ 20 버전으로 컴파일
- 아래에 지원되는 최신 C++ 컴파일러 기능으로 명시된 것 이외에는 신증하게 사용
static_assert
static_assert 키워드는 컴파일 시간 조건 검증을 위해 사용 가능
- 조건이 거짓이면 컴파일 자체를 실패, 잘못된 코드가 실행 파일로 만들어지지 않도록 차단
// 기본 문법
static_assert(조건식, "에러 메시지");// 1. [타입 크기 검증] 네트워크 패킷은 반드시 64바이트여야 한다
struct FMyPacket
{
int32 Data[16];
};
static_assert(sizeof(FMyPacket) == 64, "FMyPacket must be 64 bytes!");
// 2. [템플릿 타입 제약]
template<typename T>
void ProcessValue(T Value)
{
static_assert(std::is_integral<T>::value, "T must be an integral type");
// 정수형 타입만 허용
}
// 3. [엔진 내부 규칙 강제] UObject 파생 클래스만 허용
template<typename T>
class TMyManager
{
static_assert(TIsDerivedFrom<T, UObject>::IsDerived, "T must derive from UObject");
};override & final
사용을 강력히 권합니다.
nullptr
C 스타일의 NULL -> nullptr을 사용
auto
auto는 왠만하면 사용하지 말기
- 변수에 람다 바인딩
- 이터레이터 타입이 장황해 가독성에 악영향을 끼칠때
auto it2 = names.begin(); - (고급 ?) 템플릿 코드에서 표현식의 타입을 쉽게 식별 못할때
범위 기반 for
사용을 추천합니다.
TMap<FString, int32> MyMap;
// 기존 스타일
for (auto It = MyMap.CreateIterator(); It; ++It)
{
UE_LOG(LogCategory, Log, TEXT("Key: %s, Value: %d"), It.Key(), *It.Value());
}
// 새 스타일
for (TPair<FString, int32>& Kvp : MyMap)
{
UE_LOG(LogCategory, Log, TEXT("Key: %s, Value: %d"), *Kvp.Key, Kvp.Value);
}
강 - 타입 Enum
가능하면 enum class 를 사용
// 기존 열거형
UENUM()
namespace EThing
{
enum Type
{
Thing1,
Thing2
};
}
// 새 열거형
UENUM()
enum class EThing : uint8
{
Thing1,
Thing2
}디폴트 멤머 이니셜라이저
class UTeaOptions : public UObject
{
GENERATED_BODY()
public:
UPROPERTY()
int32 MaximumNumberOfCupsPerDay = 10; // ← 여기서 바로 초기화
};위와 같이 바로 초기화 하면, 생성자에서 멤버 초기화가 필요 없다.
- 엔진 코드: 디폴트 멤머 이니셜라이저 자주 사용 X
- 게임 코드: 개발자가 직접 관리하니, 가독성 & 편의성 측면에서 자주 사용 O
configINI환경설정 파일을 통해 관리 하는것도 권장
물리적 종속성 (Physical Dependencies)
- 파일 이름 : 접두사 X
UScene.cpp->Scene.cpp - 헤더 관리
- 모든 헤더에
#pragma once넣어 중복 include 방지 - 헤더가 늘어나면 컴파일 시간이 증가 (가능하면 적게 include)
- 모든 헤더에
캡슐화
가능하면 private으로 선언, getter/setter로 접근
일반적인 스타일 문제
- 문자열은 꼭
TEXT()매크로 사용 - 포인터 선언은 무조건
FShaderType* Ptr구조로Find In Files검색에 용이
- 헤더에 특수한 스태틱 변수 정의 X
// SomeModule.h
static const FString GUsefulNamedString = TEXT("String"); // 각 cpp파일에서 복사본이 생김
// 위 헤더를 참조하는 모든 인스턴스들이 컴파일 됨
// 아래와 같이 개선해야함
// SomeModule.h
extern SOMEMODULE_API const FString GUsefulNamedString; // 링크 단계에서 찾으라고 명시
// SomeModule.cpp
const FString GUsefulNamedString = TEXT("String");API 디자인 가이드라인
부울 함수 파라미터 피하기
- 호출하는 쪽에서
flase,ture가 각각 무엇을 의미하는지 알기 어려움 - API 확장성이 부족함
MakeCupOfTea(Tea, false, true, true);
MakeCupOfTea(Tea, false, true, true, false, true, ...);- (개선) 열거형 플래그 사용
enum class ETeaFlags
{
None,
Milk = 0x01,
Sugar = 0x02,
Honey = 0x04,
Lemon = 0x08
};
ENUM_CLASS_FLAGS(ETeaFlags)
FCup* MakeCupOfTea(FTea* Tea, ETeaFlags Flags = ETeaFlags::None);
// 호출 예시
FCup* Cup = MakeCupOfTea(Tea, ETeaFlags::Milk | ETeaFlags::Honey);인터페이스 클래스 규칙
- 접두사
I활용 - 멤버 변수 X
- 항상 추상형
// 리플렉션용 껍데기
UINTERFACE(MinimalAPI)
class UDrinkable : public UInterface
{
GENERATED_BODY()
};
// 실제 인터페이스
class IDrinkable
{
public:
virtual void Drink() = 0; // 반드시 구현해야 하는 계약
};
// 실제 구현 클래스 UCup
UCLASS()
class UCup : public UObject, public IDrinkable
{
GENERATED_BODY()
public:
virtual void Drink() override
{
UE_LOG(LogTemp, Log, TEXT("Cup is being drunk!"));
}
};정리
public에서private로이어지는 클래스 체계 (Organization) 준수- 명명 규칙
- 파스칼 케이싱(Pascal Casing)
- 소문자를 가급적 사용하지 말고, 공백 및 언더스코어 없음
- 모든 클래스와 구조체에는 고유 접두사
- 코드의 명확성
- 파라미터에 가급적
InOut접두사 const지시자 (directive) 적극적 활용- 레퍼런스를 통한 복사 방지
auto키워드 가급적 자제
- 파라미터에 가급적
- Find In Files 활용
- 헤더 파일 및
#include구문 의존성 최소화 시켜 주의 깊게 다루기

