-
Notifications
You must be signed in to change notification settings - Fork 6
Cpp Coding Standard
클래스는 기본적으로 독자의 마음으로 작성해야한다. 대부분의 독자들은 클래스의 public
한 부분을 사용할 것이므로 public
이 먼저 나오고 나서 protected
, private
순으로 나와야한다.
모든 소스파일(.h, .cpp 등)은 반드시 첫번째 줄에 저작권 표시를 해야한다:
/*!
* Copyright (c) 2021 SWTube. All rights reserved.
* Licensed under the GPL-3.0 License. See LICENSE file in the project root for license information.
*/
기본적으로 변수, 메서드, 클래스들의 이름은 명확해야하고, 구체적이며, 애매해서는 안된다. 클래스가 널리 쓰이면 쓰일 수록 더 좋고 구체적인 이름을 사용하라. 줄임말은 거르는 것이 좋다.
-
클래스와 구조체의 이름은 파스칼 표기법을 따른다
class PlayerManager; struct AnimationInfo;
-
지역 변수 그리고 함수의 매개 변수의 이름은 카멜 표기법을 따른다
void SomeMethod(const int someParameter) { int someNumber; int id; }
-
메서드 또는 함수 이름은 동사로 시작한다.
-
public
메서드의 이름은 파스칼 표기법을 따른다public: void DoSomething();
-
그 외 다른 메서드의 이름은 카멜 표기법을 따른다
private: void doSomething();
-
-
절차(반환값이 없는 함수)는 반드시 명확한 의미를 갖는 타동사와 목적어를 사용한다. 다만, 해당 함수가 클래스의 메서드이기 때문에 문맥상 타동사의 목적어가 분명할 때는 예외로 한다
-
불리언형을 반환하는 함수의 경우 질문을 하는 형식으로 작성한다
bool IsVisible(); bool ShouldClearBuffer();
-
함수의 이름은 반드시 반환하는 것이 어떤 의미를 갖는지를 알 수 있도록 지어야한다
// 체크해서 true면 뭔 뜻인데? bool CheckTea(FTea Tea); // 아~ true면 차가 fresh하단 뜻이군 bool IsTeaFresh(FTea Tea);
-
상수 또는
#define
으로 정의된 상수의 이름은 모두 대문자로 하되 밑줄로 각 단어를 분리한다constexpr int SOME_CONSTANT = 1;
-
네임스페이스는 모두 소문자로 작성한다
namespace abc{};
-
부울(boolean)형 변수는 앞에
b
를 붙인다bool bFired; // 지역 변수와 public 멤버 변수의 경우 bool mbFired; // 클래스의 private 멤버 변수의 경우
-
인터페이스를 선언할 때는 앞에
I
를 붙인다class ISomeInterface;
-
열거형을 선언할 때는 앞에
e
를 붙인다enum class eDirection { North, South };
-
클래스 멤버 변수 명은 앞에
m
을 붙인다class Employee { protected: int mDepartmentID; private: int mAge; };
-
goto
레이블 명은 모두 대문자로 하되 밑줄로 각 단어를 분리한다goto MY_LABEL; // ... MY_LABEL: std::cout << "Magic! << std::endl; return 0;
-
값을 반환하는 함수의 이름은 무엇을 반환하는지 알 수 있게 짓는다
-
단순히 반복문에 사용되는 변수가 아닌 경우엔
i
,e
같은 변수명 대신index
,employee
처럼 변수에 저장되는 데이터를 한 눈에 알아볼 수 있는 변수명을 사용한다. -
뒤에 추가적인 단어가 오지 않는 경우 줄임말은 모두 대문자로 표기한다
int OrderID; int HttpCode;
-
구조체는 오직
public
멤버 변수만 가질 수 있다. 구조체의 멤버 변수명은 파스칼 표기법을 따르며, 구조체 안에서는 함수는 사용하지 않는다 -
재귀 함수는 일므 뒤에
Recursive
를 붙인다void FibonacciRecursive();
-
파일 이름은 대소문자까지 포함해서 반드시 클래스 이름과 일치해야 한다
class PlayerAnimation; PlayerAnimation.cpp PlayerAnimation.h
-
여러 파일이 하나의 클래스를 이룰 때, 파일 이름은 클래스 이름으로 시작하고, 그 뒤에 밑줄과 세부 항목 이름을 붙인다
class RenderWorld; RenderWorld_load.cpp RenderWorld_demo.cpp RenderWorld_portals.cpp
-
Reverse OOP 패턴을 사용할 때, 플랫폼 전용 클래스는 위 항목과 비슷한 명명 규칙을 사용한다
class Renderer; Renderer.h // 게임에서 호출되는 모든 Renderer 인터페이스 Renderer.cpp // 모든 플랫폼 용 Renderer 구현 소스 Renderer_gl.h // Renderer가 호출하는 RendererGL 인터페이스 Renderer_gl.cpp // RendererGL 구현 소스
-
비트 플래그 열거형은 이름 뒤에
Flags
를 붙인다enum class eVisibilityFlags { }
-
변수 가리기(variable shadowing)은 허용되지 않는다. 외부 변수가 동일한 이름을 사용 중이라면 내부 변수에는 다른 이름을 사용한다
class SomeClass { public: int32_t Count; public: void Func(const int32_t Count) { for (int32_t count = 0; count != 10; ++count) { // Use Count } } }
-
클래스 멤버 변수에 접근할 때는 항상 setter와 getter를 사용한다
-
틀린 방식:
class Employee { public: string Name; };
-
올바른 방식:
class Employee { public: const string& GetName() const; void SetName(const string& name); private: string mName; };
-
-
외부 헤더 파일을 인클루드 할 때는
#include <>
을 사용, 자체적으로 만든 헤더 파일을 인클루드 할 때는#include ""
를 사용한다. -
외부 헤더 파일을 먼저 인클루드한 뒤, 내부 헤더 파일을 인클루드를 할 때, 가능하다면 알파벳 순서를 따른다
-
모든 헤더 파일 첫 번째 줄에
#pragma once
를 기재한다 -
지역 변수를 선언할 때는 그 지역 변수를 사용하는 코드와 동일한 줄(스코프)에 선언하는 것을 원칙으로 한다
-
double
이 반드시 필요한 경우가 아닌 이상 부동 소수점 값에f
를 붙여준다float f = 0.5f;
-
switch
case
문에는 항상default
를 넣는다 -
switch
case
문 끝에break;
를 넣지 않고 그 바로 아래case
문의 코드를 실행하고 싶은 경우, 미리 정의해둔FALLTHROUGH
매크로를 추가한다. 단,case
문 안에 코드가 없는 경우는 예외이다. 이는 C++17 사양에서[[fallthrough]]
애트리뷰트로 대체될 것이다.-
C++17 이전
switch (number) { case 0: DoSomething(); FALLTHROUGH case 1: DoFallthrough(); break; case 2: case 3: DoNotFallthrough(); break; default: break; }
-
C++17 이후
switch (number) { case 0: DoSomething(); [[fallthrough]]; case 1: DoFallthrough(); break; case 2: case 3: DoNotFallthrough(); break; default: break; }
-
-
default
case
가 절대 실행될 일이 없는 경우,default
case
안에Assert(false);
란 코드를 추가한다.Assert()
는 직접 구현하면 그 안에서 릴리즈 빌드 시 최적화 힌트를 추가할 수 있다switch (type) { case 1: ... break; default: Assert(false, "unknown type"); break; }
-
원칙적으로 모든 곳에
const
를 사용한다. 여기에는 지역 변수와 함수 매개 변수도 포함한다 -
개체를 수정하지 않는 멤버 함수에는 모두
const
를 붙인다int GetAge() const;
-
값(value) 형식의 변수를
const
로 반환하지 않는다. 포인터나 참조(reference)를 반환할 경우에만const
반환을 한다 -
클래스 안에서 멤버 변수와 메서드의 등장 순서는 다음을 따른다
-
friend
클래스들 -
public
메서드들 -
protected
메서드들 -
private
메서드들 -
protected
변수들 -
private
변수들
-
-
대부분의 경우 함수 오버로딩을 피한다
-
틀린 방식
const Anim* GetAnim(const int index) const; const Anim* GetAnim(const char* name) const;
-
올바른 방식
const Anim* GetAnimByIndex(const int index) const; const Anim* GetAnimByName(const char* name) const;
-
-
const
반환을 위한 함수 오버로딩은 허용한다Anim* GetAnimByIndex(const int index); const Anim* GetAnimByIndex(const int index) const;
-
const_cast
를 직접적으로 사용하지 않는다. 대신const
인 개제츨 수정 가능한 형태로 변환해서 반환하는 함수를 만든다 -
클래스는 각각 독립된 소스 파일에 있어야 한다. 단, 작은 클래스 몇 개를 한 파일에 같이 넣어 두는 것이 상식적일 경우 예외를 허용한다
-
표준 C
assert
대신에 자신만의Assert
버전을 구현한다 -
특정 조건이 반드시 충족되어야 한다고 가정(assertion)하고 짠 코드 모든 곳에
assert
를 사용한다.Assert
는 복구 불가능한 조건이다.Assert
는 릴리즈 빌드에서[__assume](https://docs.microsoft.com/en-us/cpp/intrinsics/assume?view=msvc-160)
키워드로 대체하여 컴파일러에 최적화 힌트를 줄 수 있다 -
모든 메모리 할당은 직접 구현한
New
,Delete
키워드를 통해 호출한다 -
memeset
,memcpy
,memmove
와 같은 메모리 연산 역시 우리 고유의MemSet
,MemCpy
,MemMove
키워드를 통해 호출해야 한다 -
어떤 이유로든 매개변수로
nullptr
가 넘어올 수 있는 경우가 아니라면 포인터 대신 참조자(&
)를 사용하는 것을 원칙으로 한다 (예외는 다음 항목을 참고) -
함수에서 매개변수를 통해 값을 반환할 때(
out
매개변수)는 포인터를 사용하며, 매개변수 이름 앞에out
을 붙인다-
함수
void GetScreenDimension(uint32_t* const outWidth, uint32_t* const outHeight) { }
-
호출
uint32_t width; uint32_t height; GetScreenDimension(&width, &height);
-
-
위 항목의
out
매개변수는 반드시null
이 아니어야 한다 (함수 내부에서if
문 대신assert
를 사용할 것)void GetScreenDimension(uint32_t* const outWidth, uint32_t* const outHeight) { Assert(outWidth); Assert(outHeight); }
-
매개변수가 클래스 내부에서 저장될 때는 포인터를 사용한다
void AddMesh(Mesh* const mesh) { mMeshCollection.push_back(mesh); }
-
매개변수가
void
포인터야 하는 경우는 포인터를 사용한다void Update(void* const something) { }
-
특정 크기(예를 들어 데이터 멤버의 직렬화를 위한 크기)가 필요하지 않은 한 열거형에 크기 지정자를 추가하지 않는다
enum class eDirection : uint8_t { North, South }
-
디폴트 매개 변수 대신 함수 오버로딩을 선호한다
-
디폴트 매개 변수를 사용하는 경우,
nullptr
나false
,0
같이 비트 패턴이 0인 값을 사용한다 -
가능한 고정된 크기(size)의 컨테이너를 사용한다
-
동적 컨테이너를 사용해야 한다면 가능한 한 미리 `reserve()``를 호출한다.
-
#define
으로 정의된 상수는 항상 괄호로 감싸준다#define NUM_CLASSES (1)
-
상수는
#define
보다const
상수 변수로 선언한다 -
클래스를 상호 참조할 때는
#include
보다 전방선언(forward declaration)을 최대한 이용한다 -
모든 컴파일러 경고는 반드시 고친다
-
한 줄에 변수 하나만 선언한다
-
틀린 방식
int counter = 0, index = 0;
-
올바른 방식
int counter = 0; int index = 0;
-
이래야 각 변수에 대해 주석을 남겨 이 변수가 어떤 의미인지를 알려줄 수 있기 때문이다
-
-
지역 객체를 반환할 때 NRVO의 이점을 활용한다. 이는 함수 내에 하나의 return문 만 쓴다는 것을 의미하며, 이것은 값으로 객체를 반환할 때만 적용된다.
-
struct
나class
에서 초기화 후 값 변경을 막으려고const
멤버 변수를 쓰지 않는다. 참조(&
) 멤버변수의 경우도 마찬가지 -
멤버 변수를 초기화할 때는 초기화 리스트를 사용하는 것을 기본으로 한다
-
컴파일 도중 assertion이 필요하다면
static_assert
를 사용한다 -
override
와final
키워드를 반드시 사용한다 -
항상
enum class
를 사용한다enum class eDirection { North, South }
-
가능한
Assert
대신static_assert
를 사용한다. -
포인터에
NULL
대신nullptr
를 사용합니다. -
개체의 수명이 클래스 내에서만 처리되는 경우
unique_ptr
를 사용한다. (즉, 개체 생성은 생성자에서, 개체 파괴는 소멸자에서) -
적용 가능한 곳이라면 범위기반
for
문을 사용한다. -
반복자나
new
키워드가 같은 줄에 있어서, 어떤 개체가 만들어지는 지 명확하게 드러나는 경우가 아니라면auto
키워드를 사용하지 않는다. -
std::move
를 사용하여 수동으로 반환 값을 최적화하지 않는다. 이럴 경우, 자동 NRVO 최적화가 적용되지 않는다. -
이동 생성자(move constructor)와 이동 대입 연산자(move assignment operator)를 사용해도 된다.
-
단순 상수 변수에는
const
대신constexpr
을 사용한다.적용 전:
const int DEFAULT_BUFFER_SIZE = 65536;
적용 후:
constexpr int DEFAULT_BUFFER_SIZE = 65536;
-
람다 함수의 경우
sort
등의 함수에 매개변수로 전달하는 등이 아니라면 지양하는 것이 좋으며, 사용하더라도 길이가 과도하게 길어서는 안된다
-
include
전처리문 블록과 코드 본문 사이에 반드시 빈 줄이 있어야 한다 -
탭(tab)은 비주얼 스튜디오 기본값을 사용하며, 비주얼 스튜디오를 사용하지 않을 시 띄어쓰기 4칸을 탭으로 사용한다.
-
중괄호(
{
)를 열 때는 언제나 새로운 줄에 연다. -
중괄호 안(
{ }
)에 코드가 한 줄만 있더라도 반드시 중괄호를 사용한다.if (bSomething) { return; }
-
if
-else
블록을 사용할 경우else
는if
의 중괄호 다음 줄에서부터 작성한다if (bHaveUnrealLicense) { InsertYourGameHere(); } else { CallMarkRein(); }
-
포인터나 참조 기호는 자료형에 붙인다.
int& number; int* number;
-
초기화 리스트를 이용해 멤버 변수를 초기화할 때는 아래와 같은 포맷을 따라 한 줄에 변수 하나씩 초기화한다.
- 틀린 방식:
MyClass::MyClass(const int var1, const int var2) :mVar1(var1), mVar2(var2), mVar3(0) {
- 올바른 방식:
MyClass::MyClass(const int var1, const int var2) : mVar1(var1) , mVar2(var2) , mVar3(0) {
- Visual C++: 프로젝트 설정을 변경하려면 항상 속성 시트(property sheets)에서 변경 한다.
- 프로젝트 설정에서 컴파일 경고를 비활성화 하지 않는다. 그 대신, 코드에서
#pragma
를 사용한다.
<stdint>
에 정의된 C++11 이후 추가된 형들을 사용한다
-
bool
: 불리언형 (bool
의 크기를 절대로 추정해서는 안된다). - TODO(캐릭터 관련 추가 필요)
-
uint8_t
: 무부호 바이트 (1 바이트). -
int8_t
: 유부호 바이트 (1 바이트). -
uint16_t
: 무부호 "단정수short" (2 바이트). -
int16_t
: 유부호 "단정수short" (2 바이트). -
uint23_t
: 무부호 정수 (4 바이트). -
int32_t
: 유부호 정수 (4 바이트). -
uint64_t
: 무부호 "4배 단어quad word" (8 바이트). (Microsoft의 Window.h에WORD
라는 16비트 무부호 정수의 4배 크기를 갖음을 의미) -
int64_t
: 유부호 "4배 단어quad words" (8 바이트). -
float
: 단정밀도 고정 소수점 (4 바이트). -
double
: 2배정밀도 고정 소수점 (8 바이트).
-
애초에 코드 자체가 document가 될 수 있게 작성하라
// Bad: t = s + l - b; // Good: TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;
-
의미있는 주석을 작성하자
// Bad: // increment Leaves ++Leaves; // Good: // we know there is another tea leaf ++Leaves;
-
코드를 제대로 못 짠 거에 주석을 넣어주지 말라. 차라리 다시 코드를 짜라.
// Bad: // total number of leaves is sum of // small and large leaves less the // number of leaves that are both t = s + l - b; // Good: TotalLeaves = SmallLeaves + LargeLeaves - SmallAndLargeLeaves;
-
코드와 내용이 상반되어서는 안된다
// Bad: // never increment Leaves! ++Leaves; // Good: // we know there is another tea leaf ++Leaves;