본문 바로가기
Unreal/Manual

Unreal C++ 코딩 표준 지침

by Dev_카페인 2023. 10. 25.
반응형

[Unreal/C++] 코딩 표준 지침

 

이 글은 언리얼 엔진 문서 기반으로 작성되었습니다.

 

코딩 표준

언리얼 엔진 4 코드베이스에 에픽게임스가 사용하는 표준과 규칙입니다.

docs.unrealengine.com

 

 

코딩 규칙의 중요성

  • 소프트웨어가 수명을 지속하는 동안 들어가는 경비의 80%는 유지 보수비이다.
  • 원저자(초기 개발자)가 소프트웨어의 수명이 다할 때까지 관리하는 일은 거의 없다.
  • 코딩 규칙을 따랐을 경우 한층 읽기 쉽도록 해주므로, 보다 빨리 그리고 철저하게 이해할 수 있다.

 

이에 따라 작성하는 사람 보다 읽는 사람을 염두에 두고 체계를 잡아야 한다.

읽는 사람 대부분은 클래스의 공용 인터페이스를 사용하기 때문에,

- 읽기 쉬운 public을 상단에 선언하고 private를 하단에 작성한다.

 

 

작명 규칙

  • 각 단어의 첫 글자는 대문자로 작성 한다.
  • 단어 사이에 (보통)공백을 띄우지 않는다.
  • 유형과 변수명은 명사로 작성한다.
  • 메서드 이름은 동사로 작성한다. (메서드가 하는 일이나 반환값을 설명한다.)
  • 변수 이름과 구분하기 위해 유형 이름을 대문자 한 글자로 나타내는 접두사를 붙인다. (C# 접두사 생략)
  • 이름은 명확하고 서술적으로 표현한다.
  • 과도한 축약은 피한다.
  • 모든 변수에 대한 설명을 코멘트로 붙일 수 있도록 한 번에 하나씩 선언한다.
  • 함수 파라미터 중 레퍼런스로 전달된 이후 출력될 경우 이름 앞에 "Out" 접두사를 붙이는 것을 권고한다.

 

접두사

클래스 접두사
템플릿(Template) T
UObject 상속 U
AActor 상속 A
SWidget 상속 S
추상 인터페이스 I
Enum (열거형) E
Boolean b
그 외 대부분 F
일부 서브 시스템 예외 접두사
구조체 Typedef F
UObject Typedef U
템플릿 Typedef 적절하게

 

 

이식 가능한 (Portable) C++ 코드

  • bool for boolean values (NEVER assume the size of bool). BOOL will not compile.
  • TCHAR for a character (NEVER assume the size of TCHAR).
  • uint8 for unsigned bytes (1 byte).
  • int8 for signed bytes (1 byte).
  • uint16 for unsigned "shorts" (2 bytes).
  • int16 for signed "shorts" (2 bytes).
  • uint32 for unsigned ints (4 bytes).
  • int32 for signed ints (4 bytes).
  • uint64 for unsigned "quad words" (8 bytes).
  • int64 for signed "quad words" (8 bytes).
  • float for single precision floating point (4 bytes).
  • double for double precision floating point (8 bytes).
  • PTRINT for an integer that may hold a pointer (NEVER assume the size of PTRINT).
C++의 int와 unsigned int 유형은 플랫폼에 따라 크기가 변할 수 있다.
정수 범위가 중요하지 않은 경우 사용해도 무방하지만, 명시적으로 크기가 정해진 경우 직렬화(Serialized) 또는 복제 형식(Replicated Formats)으로 사용해야 한다.

 

 

코멘트 지침 

  • 자체적으로 설명이 되는 코드를 작성한다.
  • 사용자가 읽었을 경우 이해할 수 있게 작성한다.
  • 나쁜 코드에 코멘트를 자세하게 달지 말고 이해하기 쉬운 코드로 다시 작성한다.
  • 모순된 코멘트를 작성하지 않는다.
  • 코드 변경시 주석도 함께 업데이트 한다.
  • 함수의 코멘트는 공개적으로 선언되는 곳에 포함시킨다.
  •  

 

Const 정확도

Const는 컴파일러 지시어인 만큼 문서화되어 있으므로 모든 코드는 const가 정확하도록 노력해야 한다.

  • 함수에 의해 인수가 수정되지 않는 경우 Const로 전달한다.
  • 객체를 수정하지 않는 경우 함수를 Const로 지정한다.
  • 반복문에서 컨테이너를 수정하지 않는 경우 Const로 반복한다.
  • 포인터가 변경되지 않는 경우 Const로 지정한다. (T* const Ptr = ...;)
  • 반환형에는 Const를 사용하지 않는다.

 

C++ 언어 구문

Unreal에서 지원하는 C++ 구문은 이식성을 고려하여 컴파일러의 호환성을 위해 대부분 C++14 기능을 활용하고 있다.

그러므로 모든 컴파일러가 최신 구문을 지원하기 전까지는 업데이트 될 가능성이 적다.

아래에 나열한 지원 되고 있는 C++ 언어 구문 외 최신 C++ 언어 구문은 사용을 지양해야한다.

  • static_assert : 컴파일 시간 어설션이 필요한 경우 사용
  • override 및 final : 사용을 권고한다.
  • nullptr : 모든 경우 NULL 매크로 대신 nullptr을 사용한다.
  • auto : 명확성을 위해 최대한 사용을 하지 않는다.
  • 범위 기반 for : 코드의 가독성과 유지보수에 도움된다.
  • 람다 및 무명 함수 : 람다(Lamda)는 자유롭게 사용 가능하다.
  • enum class : enum을 대체하여 사용해야 한다.
  • MoveTemp : 주요 컨테이너 유형에는 Move 할당 연산자가 있지만, MoveTemp를 통해 명시적으로 실행 가능하다.
  • Initializers : 선언과 동시에 초기화 하는 것은 장점과 단점을 고려해 사용한다.

 

auto 키워드를 사용해도 괜찮은 경우

  • 변수에 람다를 바인딩해야 하는 경우
  • Iterator 변수의 경우
  • 템플릿 코드에서 표현식의 유형을 쉽게 식별할 수 없는 경우

 

람다 및 무명함수 사용시 주의할 점

  • 스테이트풀 람다는 함수 포인터에 할당될 수 없다.
  • 일반 함수와 같은 방식으로 문서화를 진행한다.
  • 자동 캡처보다 명시적 캡처가 좋다. ([&]  [=]) (가독성, 유지보수, 성능의 이유로 중요하다.)
  • 포인터 참조 캡처와 값 캡처가 때때로 허상 참조를 유발할 수 있다.
  • 값 캡처는 유예식이 아닌 람다에 사본을 만드는 경우 성능 문제가 있을 수 있다.
  • 잘못 캡처된 UObject 포인터는 가비지 콜렉터에 보이지 않는다.
  • 자동 캡처는 멤버 변수가 참조된 경우, 묵시적으로 this를 캡처한다.
  • 결과를 반환할 경우 명시적으로 표현한다.

 

기본 멤버 초기화 장단점

장점

  • 여러 생성자에 걸쳐 이니셜라이저를 복제할 필요가 없다.
  • 초기화 순서와 선언 순서가 섞일 일이 없다.
  • 멤버 유형, 프로퍼티 플래그, 기본 값이 모두 한 곳에 있어, 가독성과 유지보수성에 좋다.

단점

  • 기본 값을 변경하면 모든 종속 파일을 리빌드해야 한다.
  • 헤더는 엔진 패치 릴리즈에서 변경할 수 없으므로, 가능한 픽스 종류가 제한될 수 있다.
  • 모든 것을 초기화시킬 수는 없다.
  • 여기저기 나눠서 초기화 시 가독성과 유지보수성에 좋지 않다.

 

 

서드 파티 코드

엔진에서 사용하는 라이브러리의 코드를 수정할 때 변경 사항에

//@UE4 주석과 함께 변경 이유에 대한 설명을 태그해야 한다.

 

 

코드 포맷

대괄호 : 줄바꿈을 한 후 대괄호를 넣는다.

If-Else : 각 블록은 대괄호로 묶는다.

들여쓰기 : 실행 블록 별로 코드를 들여쓴다.

Tab : 공백을 사용하기 보다 Tab을 주로 이용한다.

Switch : 다음 케이스로 넘어가는지 명시적으로 밝히고, break를 넣거나 fall through 코멘트를 작성한다.

 

네임 스페이스 규칙

  • 언리얼 코드는 현재 글로벌 네임스페이스에 둘러싸여 있지 않다.
  • 서드 파티 코드를 사용하거나 포함할 때는 전역 범위에서 충돌이 일어나지 않도록 주의를 기울여야 한다.
  • Using 선언은:
    • cpp 파일에서도 전역 범위에 선언을 넣지 않는다. ("unity" 빌드 시스템에 문제가 발생할 수 있다.)
    • 다른 네임스페이스 안이나 함수 본문 안에서는 using 선언을 넣어도 괜찮다.
    • 네임스페이스 안에 using 선언을 넣는 경우, 해당 네임스페이스 다른 항목에도 적용된다.
    • 오직 위 규칙을 따를 때 using 선언을 사용해도 안전합니다.
  • 참고로 앞서 선언된 형은 각각의 네임스페이스 안에서 선언해 줘야 합니다. 그렇게 하지 않으면 링크 오류가 납니다.
  • 한 네임스페이스 안에 다수의 클래스/유형을 선언하면, 다른 전역 범위의 클래스에서 사용하기가 어려울 수 있다.
  • using 선언을 사용해서 네임스페이스 안의 특정 변수만 범위에 대한 별칭을 지정할 수 있다.
  • 언리얼 헤더 툴에는 네임스페이스가 지원되지 않으므로, UCLASS, USTRUCT 등을 정의할 때는 사용할 수 없다.

 

물리적 종속성

  • 파일 이름은 가급적 접두사를 붙이지 않는 것이 좋다.
  • 모든 헤더는 #pragma once 디렉티브(지시자)로 복수의 include 를 방지해야 한다.
  • 되도록 물리적 결합을 최소화한다. 
  • 다른 헤더의 표준 라이브러리 헤더를 포함하지 않는다.
  • 헤더를 포함하는 대신 전방 선언을 사용할 수 있다면 사용한다.
  • 포함할 때는 최대한 세밀하게 작성하세요. 전체 헤더 대신 필요한 기능 위주의 헤더 파일을 포함시킨다.
  • 세부적인 포함을 더 쉽게 만들기 위해 필요한 모든 헤더를 직접 포함시킨다.
  • 포함하는 다른 헤더에 의해 간접적으로 포함되는 헤더에 의존하지 않는다.

 

캡슐화

  • 접근 지정자를 적극적으로 활용한다.
  • 일부를 제외하고 대부분 private로 보호한다.
  • 더 이상 파생시킬 클래스가 아닌 경우 final을 사용한다.

 

일반적인 스타일 문제

  • 종속성을 최소화한다.
  • 함수는 가급적 하위 함수로 분할한다. 다수의 하위 메서드를 이해하기 수월하다.
  • 함수 선언이나 호출 위치에서 함수의 이름과 인수 목록 앞의 괄호 사이에 공백을 두지 않는다.
  • 컴파일러 경고에 주의를 기울인다. #pragma 로 억제시킬 수는 있지만, 이는 정말 마지막 수단이어야 한다.
  • 파일 끝에 빈 줄을 하나 추가한다. 모든 .cpp 와 .h 파일은 빈 줄이 있어야 gcc 에 제대로 돌아간다.
  • 디버그 코드는 일반적으로 유용하고 잘 다듬어진 상태가 아니라면 체크인하지 말아야 한다. 
  • 스트링 리터럴 주변에는 항상 TEXT() 매크로를 사용해야 한다.
  • 공통된 하위 표현식은 루프 밖으로 빼서 중복 계산을 피한다.
  • 핫 리로드 기능을 주의한다.  반복처리 시간을 줄이기 위해 종속성을 최소화한다.
  • 복잡한 표현식은 중간 변수를 사용하여 단순화한다. 
  • 포인터와 레퍼런스의 공백은 그 오른쪽에 딱 한 칸만 둬야한다.
  • 변수 음영(shadowed)은 허용되지 않는다.
  • 함수 호출에서 익명 리터럴 사용은 피한다.

 

 

API 디자인 지침

  • bool 함수 파라미터는 피해야 한다. enum class 사용을 권장한다.
  • 너무 긴 함수 파라미터 리스트는 피한다.  함수가 파라미터를 많이 받는 경우 전용 구조체 전달을 고려한다.
  • bool  FString 을 사용한 함수 오버로드는 피한다.
  • 인터페이스 (접두사가 "I" 인) 클래스는 항상 추상형이어야 하며, 멤버 변수가 있어서는 안된다.
  • 오버라이딩 메서드를 선언할 때는 virtual  override 키워드를 사용한다.

 

플랫폼별 코드

플랫폼별 코드는 항상 적합한 이름의 하위 디렉터리 아래 플랫폼별 소스 파일에 추상화 및 구현해야 한다.

 

 

 

 

 

 

 

 

반응형

'Unreal > Manual' 카테고리의 다른 글

Unreal 권장하는 에셋 명명 규칙  (0) 2023.10.25
Unreal UPROPERTY 종류  (0) 2023.10.25
Unreal C++ 컴포넌트 추가하기  (0) 2023.10.24
Unreal C++ 헤더 포함 Header Include  (0) 2023.10.24
Unreal C++, 라이브 코딩 컴파일  (0) 2023.10.23