정신과 시간의 방
작성일
2024. 11. 8. 14:56
작성자
risehyun

 

0. 언리얼 코딩 표준 공식 문서

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/epic-cplusplus-coding-standard-for-unreal-engine?application_version=5.3

 

 

1. 코딩 표준이란?

 - 표준 코드 작성 스타일로 이름 규칙, 작성 방법 등 프로그래밍 시 지켜야 하는 요소들에 대한 가이드라인

 - 코딩 스타일, 코딩 컨벤션이라고도 함

 - 기존에 확립된 표준 및 모범 사례를 준수하여 유지보수 가능한 코드를 작성하도록 하는 것이 사용 목적

 - 절대적으로 좋은 코딩 표준은 없으나 프로젝트에 지정된 코딩 표준은 반드시 따라야 함

 - 언리얼은 자체 코딩 표준이 있으며 이는 권고가 아닌 반드시 지켜야 하는 것으로 몇 가지 사항은 지키지 않으면 프로젝트 작동에 영향을 미칠 수도 있음 (ex. new 키워드가 금지되었는 데 사용하는 경우 등)

 

 

1-1 코딩 규칙이 필요한 이유

 - 소프트웨어 총 수명 비용 중 80%가 유지보수에 소모됨. 즉, 유지보수는 매우 중요함

 - 그러나 소프트웨어 수명이 다할 때까지 유지보수 담당자는 계속 변하게 됨

 - 따라서 코딩 규칙을 사용해 가독성을 높이면 유지보수를 위해 코드를 파악할 때 빠르고 완벽히 이해 가능

 - 또한 공통된 코딩 규칙을 알고 있으면 팀작업 시 이해가 쉬울 뿐만 아니라 효율성이 높아짐

 - 마지막으로 크로스 컴파일러 호환성을 위해서도 코딩 규칙이 필요함.

 

 

2. 클래스 체계

public 부분을 먼저 작성하고 private 부분을 작성한다.

 

 - 클래스는 작성자보다 읽는 사람을 염두하고 조직해야 함

 - 읽는 사람의 대부분이 클래스의 public 인터페이스를 사용함

 - 따라서 먼저 public 부분을 구현하고 그다음에 private 부분을 구현해야 함

 

 

3. 명명 규칙

 - 일반적인 명명 규칙에는 다음의 3가지 방법이 있음

   1. 파스칼 케이싱 (모든 합성어의 첫 글자를 대문자 처리) ex. UnrealEngine, UMyGameInstance
   2. 카멜 케이싱 (첫 합성어는 소문자, 나머진 대문자) ex. unrealEngine
   3. 스네이크 케이싱 (합성어 사이에 언더바 사용) ex. unreal_engine

 - 언리얼에서는 파스칼 케이싱만을 사용함 (타입, 함수, 변수 선언 시 합성어 첫 글자는 대문자만 허용)

 

ex. Health 및 UPrimitiveComponent는 올바르지만 (파스칼 케이싱 사용)

       lastMouseCoordinates(카멜 케이싱) 또는 delta_coordinates(스네이크 케이싱)은 올바르지 않음

 

3-1 언리얼 네이밍 컨벤션 세부 규칙

 - 타입 이름에는 대문자 접두사를 포함해 변수 이름과 구분

ex. FSkin > 타입 이름 / Skin > FSkin 타입의 인스턴스

 - 템플릿 클래스에는 접두사 T를 포함할 것

ex. TAttribute

 

 - UObject를 상속하는 클래스는 접두사 U를 포함

ex. UActorComponent

 

- AActor를 상속하는 클래스는 접두사 A를 포함

ex. AActor

 

- SWidget을 상속하는 클래스는 접두사 S를 포함

ex. SCompoundWidget

 

- 추상적 인터페이스 클래스는 접두사 I를 포함

ex. IAnalyticsProvider

 

- 에픽의 콘셉트가 유사한 클래스 타입(TModels 타입 특성에 첫 번째 아규먼트로 사용)에는 접두사 C 포함

ex. template <typename Concept, typename... Ts>

 

- 열거형은 접두사 E 포함

ex.

enum class EColorBits 

    ECB_Red,

    ECB_Green,

    ECB_Blue 

};

 

- bool 변수는 접두사 b를 포함 (bool 변수의 경우에만 예외적으로 소문자를 사용)

ex. bHasFadedIn

 

- 그 외 언리얼 상속이 아닌 일반 C++ 클래스나 구조체등의 대부분 클래스는 접두사 F 포함

 

- Typedef는 해당 타입에 적합한 접두사를 사용함 (그 외 템플릿 관련 내용은 공식 문서 참고)

ex. 구조체의 typedef는 F, UObject의 typedef인 경우 U 사용

 

- 매크로 이름은 모두 대문자로 구성하되 단어와 단어 사이는 언더스코어(_)로 분리하며 접두사 UE_사용

ex. #define UE_AUDIT_SPRITER_IMPORT

 

 - 각 변수에 대한 코멘트 제공을 위해 모든 변수는 한 줄에 하나씩, 자체적인 줄에서 선언

 

 - bool을 반환하는 모든 함수는 true/false 식의 질문을 해야 함

ex. IsVisible(), ShouldClearBuffer()

 

- 함수 파라미터가 레퍼런스로 전달/함수를 그 값에 쓸 경우 함수 파라미터 이름에 접두사 Out 사용

 

- In, Out등 bool 값의 파라미터를 쓰는 경우 bOutResult와 같이 In/Out 접두사 앞에 'b' 붙임 (소문자 주의)

 

- 값을 반환하는 함수는 함수명 만으로도 명확하게 반환 값을 설명해야 함

ex. bool CheckTea(FTea Tea); < 이 함수의 반환값이 대체 무슨 의미인지 정확히 알 수 없음 (X)

      bool IsTeaFresh(FTea Tea); < 함수명만 보고도 이것이 true인 경우 차가 신선한 상태임을 알 수 있음(O)

 

 

포터블 C++ 코드

- C++에서 사용하던 일반적인 int와 같은 자료형을 더욱 세분화/명시화 하여 사용함

- 주요 타입

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 크기 추정 금지)

 

 

표준 라이브러리 사용 지양 (STL 사용 X)

- 언리얼 C++에서는 STL 사용을 지양하는 대신 자체 라이브러리를 사용함

- 굳이 STL을 사용해야 한다면 쓸수는 있지만 호환성 등을 생각하면 사용하지 않는 것이 좋음

 

 

Const 정확도

- 언리얼 C++은 const의 사용을 지향하며 모든 코드에 대해 const 정확도를 맞춰야함 (공식 문서 예시 참고)

 

 

최신 C++ 언어 문법 

- 언리얼 엔진은 기본적으로 C++ 20에서 컴파일 되며 빌드 시 요구되는 최소 언어 버전은 C++17

- 컴파일 시간 어서트가 필요한 경우 static_assert 키워드를 사용할 수 있음

- override, final의 사용이 강력히 권장됨

- nullptr만을 사용해야하며 C 스타일의 NULL 매크로의 사용이 금지됨 (C++/CX에만 허용)

- 이터레이터, 변수를 람다에 바인딩 하는 경우, 템플릿 코드에서의 사용 외에는 auto 사용을 지양, 초기화 하려는 타입은 항상 명시해주어야 함 (C++ 17에서의 구조체 바인딩도 금지)
-
람다, 반복자 등을 사용할 때 코드 가독성과 유지보수성 향상을 위해 auto 가능 범위 기반 for문 사용이 추천됨

- 람다 함수의 경우 자유롭게 사용 가능

- 자동 캡처보다는 명시적(explicit) 캡처를 사용해야 함

- 열거형을 사용할 때는 enum class를 사용함

- 이동 시맨틱 사용시 std::move 대신 MoveTemp를 사용해 명시적으로 호출함

- 생성자 구문에서 값을 초기화하는 것이 지향됨

- 서드 파티 코드를 명시해야 라이브러리 새 버전으로 변경 사항을 병합하는 작업을 쉽게 할 수 있음

 

 

코드 포맷

- 중괄호는 새 줄에 중괄호를 작성하며 return 값만 있는 단일 구문 블록에 대해서도 항상 중괄호를 포함시킴

- if - else의 경우에도 마찬가지로 항상 중괄호를 사용함

 

 

탭 및 들여쓰기

- 코드 들여쓰기 시 스페이스바가 아닌 탭만을 사용하며, 탭의 크기는 스페이스 기준 4자임

 

 

Switch 문

- 다음 케이스로 넘어갈 때는 break, return문을 사용해 명시적으로 알려주어야 함 

- 항상 디폴트 케이스를 만들어 두며 디폴트 케이스 역시 뒤에 새로운 케이스가 추가될 경우를 대비해 break문을 넣음

 

 

물리적 종속성

- 파일명의 경우 기존 명명규칙과 달리 접두사를 제외하고 사용하여 원하는 파일을 식별하는데 필요한 글자수를 줄임

- 헤더에 #pragma once를 사용해 불필요한 복수의 inclue를 방지

- include시 최대한 세밀하고 적게 사용하는 것이 지향되며 전방선언으로 대체 가능한 경우 include를 사용하지 않음

- inline 함수는 사용하지 않는 파일에 대해서도 리빌드를 강제하므로 남용하지 않는 것을 지향됨 (사소한 접근자이거나 확실한 이득이 있는 것으로 보일 때만 사용)

 

 

캡슐화

- 클래스 멤버는 public/protected로 사용되어야 하는 특별한 이유가 있지 않다면 항상 private로 선언함

- 더 이상 파생시킬 클래스가 아닌 경우 명시적으로 final을 사용하는 것을 지향함

 

 

일반적인 스타일 문제 일부 (나머지는 공식 문서 참고)

- 스트링 리터럴은 항상 TEXT() 타입을 사용

- 모든 .cpp 및 .h 파일은 파일 끝에 빈 줄 하나를 만들어야 gcc와 함께 올바르게 작동됨

- 복잡한 표현식을 사용하는 경우 중간 변수를 하나 선언하고 그것을 활용해 간소화함.

- 특정 타입에 대한 모든 포인터나 레퍼런스에 빠르게 Find in Files 를 사용할 수 있도록 포인터와 레퍼런스의 스페이스는 그 오른쪽에 딱 한 칸만 두어야 함

ex. FShaderType* Ptr