정리
1. public에서 private으로 이어지는 클래스 체계(Organization)을 준수
2. 명명 규칙
a. 파스칼 케이싱을 사용한다.
b. 소문자를 가급적 사용하지 않고 공백 및 언더스코어가 없음
c. 모든 클래스와 구조체에는 고유한 접두사가 있음
3. 코드의 명확성
a. 파라미터에 가급적 In과 Out접두사를 사용해 명시
b. const 지시자(directive)의 적극적인 활용
c. 레퍼런스를 통한 복사 방지
d. auto 키워드는 가급적 자제
4. Find In Files의 활용
5. 헤더 파일 및 #include 구문은 의존성을 최소화시켜 주의 깊게 다룰 것
언리얼5 홈페이지를 방문해 코딩 표준을 읽어보자.
목표1. 언리얼 C++ 코딩 표준 이해
목표2. 언리얼 C++ 코딩 표준에서 주의할 점 확인
코딩표준이란?
규칙, 가이드라인
코딩 스타일, 컨벤션이라고도 함.
좋은 코딩 표준이란?
절대적인 건 없어. 정하고 잘 따르는 행동 자체가 중요
마치 한 사람이 만든 것처럼 보이게 해야 한다.
언리얼 엔진은 자체적으로 코딩 표준을 정했기 때문에 권고 사항이 아니라 따라야 한다.
언리얼 엔진의 소스 코드는 코딩 표준을 따라 작성이 되어 있기 때문에 기존의 방법은 버리고 따라야 한다.
구글 C++ 코딩 표준도 있어.
https://google.github.io/styleguide/cppguide.html
Google C++ Style Guide
google.github.io
한사람이 작성한 것처럼 작성해야 하는 이유는, 코드를 분석하는 시간을 줄일 수 있기 때문이다.
언리얼 C++ 코딩 표준
https://docs.unrealengine.com/5.0/ko/epic-cplusplus-coding-standard-for-unreal-engine/
코딩 표준
기존에 확립된 표준 및 모범 사례를 준수하여 유지보수 가능한 코드를 작성합니다.
docs.unrealengine.com
클래스 체계 - public 부터 쓰고, private 쓰고 구조를 잡고 시작한다.
저작권 고지 - 저작권 써있는 거 수정하지 말길
명명 규칙 - 특별한 명령 규칙 가져 → 파스칼 케이싱(합성어의 첫 글자를 대문자를 사용)
카멜, 스네이크 케이싱은 그만 둬야 한다.
타입 이름에 접두사:
- 템플릿 클래스에는 접두사 T를 포함합니다.
- UObject 에서 상속받는 클래스에는 접두사 U를 포함합니다.
- AActor 에서 상속받는 클래스에는 접두사 A를 포함합니다.
- SWidget 에서 상속받는 클래스에는 접두사 S를 포함합니다.
- 추상적 인터페이스인 클래스에는 접두사 I를 포함합니다.
- 에픽의 개념이 유사한 클래스 타입( TModels 타입 특성에 첫 번째 아규먼트로 사용)에는 접두사 C를 포함합니다.
- 열거형에는 접두사 E를 포함합니다.
- 부울 변수는 접두사 b를 포함합니다(예: bPendingDestruction 또는 bHasFadedIn ).
- 그 외 대부분의 클래스는 접두사 F를 포함합니다. 그러나 일부 서브시스템은 다른 글자를 사용하기도 합니다.(구조체, 언리얼 오브젝트를 상속받지 않은 클래스)
부울을 반환하는 모든 함수는 IsVisible() 또는 ShouldClearBuffer() 등의 true/false 질문을 해야 합니다.
In 또는 Out 파라미터도 부울인 경우 bOutResult 와 같이 In/Out 접두사 앞에 'b'를 붙입니다.
포터블 C++ 코드
- bool - 부울 값(부울 크기 추정 금지). BOOL 은 컴파일되지 않습니다.
- 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바이트)
- float - 단정밀도 부동 소수점(4바이트)
- double - 배정밀도 부동 소수점(8바이트)
- PTRINT - 포인터를 가질 수 있는 정수(PTRINT 크기 추정 금지)
C++의 int 및 부호 없는 int 타입(플랫폼에 따라 크기가 변할 수 있으나 항상 최소 너비는 32비트로 보장됨)은 정수 너비가 중요치 않은 경우라면 코드에서 사용해도 괜찮습니다. 명시적으로 크기가 정해진 타입은 여전히 시리얼라이즈 또는 리플리케이트된 포맷으로 사용해야 합니다.
표준 라이브러리 사용
범용 STL을 사용할지 팀마다 결정해야 하긴 한다.
하지만 STL을 사용하지 않는다는게 기본 원칙이다.
코멘트
Const 정확도
왠만하면 const 쓸 수 있는 곳엔 다 쓴다.
문법적으로 의미 없는 곳엔 쓰지 않는다.
읽을 때 거꾸로 읽으면 이해에 도움이 된다.
// const 포인터에서 const 이외 오브젝트 - 포인터로의 재할당은 불가하나, T는 여전히 수정 가능합니다.
T*const Ptr = ...; // Ptr 연산자에 대해 증감연산 할 수 없다는 얘기, 하지만 가리키는 데이터는 수정 가능
// 틀림
T&const Ref = ...; //
// 나쁜 예 - const 배열 반환
const TArray<FString> GetSomeArray(); // 레퍼런스가 아니라 복사가 일어나기에 나쁜 예
// 좋은 예 - const 배열로의 레퍼런스 반환
const TArray<FString>& GetSomeArray(); // 대부분 이경우 많이 사용
// 좋은 예- const 배열로의 포인터 반환
const TArray<FString>* GetSomeArray(); // 포인터가 가리키는 데이터는 바뀌면 안된다.
// 나쁜 예 - const 배열로의 const 포인터 반환
const TArray<FString>*const GetSomeArray(); // 포인터도, 포인터가 가리키는 데이터도 고칠 수 없다.
예시 포맷
규칙을 지정하면 JavaDoc이라는 프로그램에서 문서를 만든다.
최신 C++ 언어 문법
C++ 17 까진 사용 가능. 최신은 아니지만 이게 가장 많이 사용 됨
override 및 final
에러 방지 가능하므로 강력 권장
nullptr 사용
auto 키워드는 문자 소지가 될 수 있어. 왠만하면 사용 안한다. 사용할 때는 람다나 iterator 쓸 때는 사용
범위기반 for
2개 인자로 간편한 스타일로 사용하는게 좋다.
람다도 어떻게 쓰는지 인지한 상태에서 명시적으로 사용해야 한다.
Enum
enum class 가 강화된 새 열거형
// 기존 열거형
UENUM()
namespace EThing
{
enum Type
{
Thing1,
Thing2
};
}
// 새 열거형
UENUM()
enumclass EThing : uint8
{
Thing1,
Thing2
}
이전 방식도 보일 수 있으므로 염두하며 사용
STL의 move 말고 언리얼 엔진의 MoveTemp를 쓴다.
디폴트 멤버 이니셜라이저
C++에서 생성자 헤더에 기본값 지정하는 거 편해. 하지만 리얼 기본 오브젝트들은 생성자 구문에서 초기화 하는 것이 좋다.
서드 파티 코드 : 서드 파티라고 명시를 해주자.
중괄호
if (bThing)
{
return;
}
이렇게 쓰는게 좋다.
if else 구문도 마찬가지로 쓴다.
들여쓰기는 스페이스 아닌 탭으로 한다.
Switch문
가급적이면 아무것도 모르는 사람이 본다고 생각하고 명시적으로 지정하는게 좋다.
barek 잘 넣기
네임스페이스
게임에선 별로 쓸 일이 없을 거 같아.
물리적 종속성
클래스 이름에는 접두사가 붙었었지만 파일 이름에는 접두사가 없는게 낫다.
모든 헤더는 #pragma once 를 지정해줘야 한다.
헤더 꼼꼼하게 살펴 봐야 한다. #include가 복사를 하는 거라. 헤더 많으면 복사량 많아져서 컴파일 오래걸려져. 가급적 #incldue 세밀하게 설계해야 한다.
이 때 많이 쓰는게 전방 선언
가급적이면 적은수의 헤더를 include해라
캡슐화
왠만하면 private으로 선언하고 getter, setter로 접근하는게 좋다.
더 이상 파생시킬 클래스가 아니면 final 사용
일반적 스타일 문제
의존성, 종속성의 문제가 중요하다. 어떤 클래스가 있을 때 다른 클래스에 영향 안주게끔 설계하는 것이 기본적인 원칙으로 생각해서 진행하면 된다.
문자열을 표현할 때는 TEXT() 매크로를 사용한다.
코드가 길어지더라도 보기 편하게 바꿔주자.
포인터를 선언할 때 FShaderType* Ptr 이렇게 선언해야 한다. 왜냐하면 나중에 언리얼 엔진 숙련도가 올라갈수록 소스 코드를 많이 볼텐데 그 때 많이 쓰는 기능 중 하나가 Find in Files다. 솔루션의 모든 파일들을 검색하는 거. 이 때 언리얼이 어떻게 동작하는지 보기 위해 Type을 기준으로 검색하는 경우가 많은데 FShaderType *Ptr이나 FShaderType * Ptr 이렇게 띄어쓰기 다르게 쓰면 원하는 타입 검색하기가 힘들다. 레퍼런스도 마찬가지고 규칙을 지켜서 써야 한다.
애매 모호한 건 길어도 좋으니까 항상 명확하게 이해될 수 있게 코드 작성 하자.
헤더에 static 변수 선언할 때는 헤더 참조하는 모든 인스턴스들이 컴파일 되기 떄문에 extern으로 빼줘야 한다.
// SomeModule.h
staticconst FString GUsefulNamedString = TEXT("String");
이러한 코드는 다음으로 대체해야 함
// SomeModule.h
extern SOMEMODULE_APIconst FString GUsefulNamedString;
// SomeModule.cpp
const FString GUsefulNamedString = TEXT("String");
메모리 할당되는 건 cpp 파일에서 구현 해주도록 한다.
API 디자인 가이드라인
bool 형태를 인자로 넣는 걸 피하고, 카테고리를 지정하는 경우가 많다.
열거형으로 지정하는 게 좋다. 비트플래그로 조합할 수도 있다.
// 기존 스타일
FCup* MakeCupOfTea(FTea* Tea,bool bAddSugar =false,bool bAddMilk =false,bool bAddHoney =false,bool bAddLemon =false);
FCup* Cup = MakeCupOfTea(Tea,false,true,true);
// 새 스타일
enumclass 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);
인터페이스 자체에 대한 문법이 없어. 멤버 변수 넣을수 있지만 가급적 넣지 않고 인터페이스로 쓰기.
오버라이딩은 overriding 꼭 명시하기.
플랫폼별 코드
에픽 코드는 플래폼 별로 기능이 구현되어 있는 경우가 많다. 일반 기능에 영향을 미치지 않게 설계를 해달라. 라는 것으로 이해를 해주면 된다.
게임 작업시에는 별로 신경 안써도 되지만 플래폼 별로 특수한 작업 진행할 때 참고
'Unreal engine > Unreal C++' 카테고리의 다른 글
| 1_5_언리얼 오브젝트의 이해_언리얼 오브젝트 리플렉션 시스템1 (0) | 2023.09.18 |
|---|---|
| 1_4_언리얼 오브젝트의 이해_언리얼 오브젝트 기초 (0) | 2023.09.18 |
| 1_3_언리얼 오브젝트의 이해_언리얼C++ 기본타입과 문자열 (0) | 2023.09.15 |
| 1_1_언리얼 오브젝트의 이해_헬로 언리얼 (0) | 2023.09.14 |
| 0_1. 강의소개: 언리얼 프로그래밍 공부법 (0) | 2023.09.11 |
댓글