11장 코드 형식 (Formatting)
코딩 스타일과 형식은 다소 임의적일 수 있지만, 모든 사람이 같은 스타일을 사용하면 프로젝트를 훨씬 쉽게 이해할 수 있다. 개개인은 모든 형식 규칙에 동의하지 않을 수도 있고, 일부 규칙이 익숙해지는 데 시간이 걸릴 수 있지만, 모든 프로젝트 기여자가 규칙을 준수해야 서로의 코드를 읽고 이해하기 쉬워진다.
코드를 올바르게 형식화하는 데 도움이 되도록, emacs 설정 파일을 제공하고 있다.
프로젝트의 일관된 형식을 유지함으로써, 가독성이 향상되고 협업이 원활해진다.
11.1 줄 길이 (Line Length)
코드의 각 줄은 최대 80자를 초과하지 않도록 한다.
이 규칙은 논란이 있을 수 있으나, 많은 기존 코드가 이미 이를 준수하고 있으며, 일관성을 유지하는 것이 중요하다.
- 80자 규칙을 선호하는 사람들은 화면 크기를 변경해야 하는 불편을 피하고자 하며, 여러 코드 창을 동시에 띄워놓고 작업하는 경우가 많기 때문에 코드 창 너비를 넓히기 어렵다고 주장한다. 대부분의 작업 환경이 특정 최대 너비를 가정하고 있고, 80자 제한은 전통적인 표준이기 때문에 이 규칙을 따르는 것을 선호한다.
- 80자 제한을 변경하자는 의견은 더 넓은 줄이 가독성을 높일 수 있다는 입장이다. 80자 제한은 오래된 전통이며, 현대 모니터는 충분히 넓어서 더 긴 줄도 쉽게 표시할 수 있다는 주장이다.
80자를 초과할 수 있는 경우
다음과 같은 경우에는 예외로 줄이 80자를 초과할 수 있다.
- 댓글 줄: 읽기 쉬움, 붙여넣기 편리함, 자동 링크 기능이 손상되지 않는 경우가 아니면 분할을 피한다. 예를 들어, 80자를 초과하는 명령어나 URL 등이 포함된 줄은 예외로 인정될 수 있다.
- 문자열 리터럴: 줄바꿈을 적용할 수 없는 문자열 리터럴이 있을 경우, 예를 들어 URI 등 중요한 요소나 언어가 포함된 경우에는 그대로 유지한다. 특히, 다중 행 리터럴에서 줄바꿈이 중요한 경우(예: 도움말 메시지)에는 읽기 및 검색 가능성이 손상되지 않도록 줄을 분할하지 않는다. 이러한 문자열 리터럴은 일반적으로 네임스페이스 범위 상단에 위치해야 하며, 자동 포매팅 도구가 인식하지 못하는 경우 도구를 일시적으로 비활성화할 수 있다.
- include 문: 줄을 넘어갈 수 있다.
- 헤더 가드: 헤더 파일에서 #ifndef/#define/#endif를 사용해 정의하는 헤더 가드도 예외로 간주된다.
- using 선언
11.2 비 ASCII 문자 (Non-ASCII Characters)
비 ASCII 문자는 드물게 사용되어야 하며, 반드시 UTF-8 인코딩을 사용해야 한다.
소스 코드에 사용자에게 표시되는 텍스트를 하드코딩하지 않도록 한다. 예를 들어 영어라도 하드코딩을 피하는 것이 바람직하다. 그러나 비 ASCII 문자가 필요한 경우가 있다. 예를 들어 외국 소스의 데이터 파일을 파싱하는 코드에서 해당 데이터 파일에 사용된 비 ASCII 구분자를 하드코딩해야 하는 경우, 또는 유닛 테스트 코드에서 비 ASCII 문자열을 사용하는 경우가 있다. 이러한 상황에서는 UTF-8을 사용해야 한다. UTF-8은 ASCII를 초과하는 문자를 처리할 수 있는 대부분의 도구에서 이해할 수 있는 인코딩 방식이다.
16진수 인코딩도 허용되며, 가독성을 높이는 경우 권장된다. 예를 들어 "\xEF\xBB\xBF" 또는 "\uFEFF"와 같이 Unicode 제로 너비 비브레이크 공간을 표현할 수 있다. 이를 UTF-8로 직접 포함할 경우 소스 코드에서 보이지 않으므로, 16진수 인코딩이 가독성을 높이는 경우가 있다.
u8 접두사는 가능한 한 피하는 것이 좋다. C++20에서는 C++17과 다른 의미로 해석되어 char8_t 배열을 생성하며, C++23에서 또 한 번 의미가 변경될 예정이다.
또한, char16_t 및 char32_t 문자 타입은 UTF-8 텍스트에 적합하지 않기 때문에 사용을 피해야 한다. 유사한 이유로 wchar_t도 사용하지 말아야 하며, 단, Windows API와 상호작용하는 코드 작성 시에는 예외적으로 사용할 수 있다.
11.3 공백과 탭 (Spaces vs. Tabs)
공백만을 사용하며, 들여쓰기는 2칸으로 한다.
코드에서는 탭을 사용하지 않고 공백으로만 들여쓴다. 탭 키를 눌렀을 때 공백이 삽입되도록 에디터를 설정하는 것이 좋다.
11.4 함수 선언 및 정의 (Function Declarations and Definitions)
함수의 반환 타입과 이름은 같은 줄에, 매개변수도 가능한 한 같은 줄에 작성하며, 한 줄에 모두 넣기 어려운 경우 매개변수를 함수 호출 인자처럼 줄을 맞춰서 감싼다.
예시
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
DoSomething();
...
}
한 줄에 적기 어려운 경우
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
Type par_name3) {
DoSomething();
...
}
첫 번째 매개변수까지 한 줄에 넣기 어렵다면
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4칸 들여쓰기
Type par_name2,
Type par_name3) {
DoSomething(); // 2칸 들여쓰기
...
}
주의사항
- 좋은 매개변수 이름을 사용한다.
- 매개변수가 함수 정의에서 사용되지 않을 경우 이름을 생략할 수 있다.
- 반환 타입과 함수 이름을 한 줄에 모두 넣기 어렵다면 반환 타입과 함수 이름 사이에서 줄을 바꾼다.
- 반환 타입과 함수 이름 사이에서 줄을 바꿀 경우 들여쓰기를 하지 않는다.
- 여는 괄호는 함수 이름과 같은 줄에 위치한다.
- 함수 이름과 여는 괄호 사이에는 공백이 없다.
- 매개변수 앞뒤로는 공백이 없다.
- 여는 중괄호는 마지막 줄 끝에 위치하며, 닫는 중괄호는 줄을 바꾸거나 여는 중괄호와 같은 줄에 위치한다.
- 닫는 괄호와 여는 중괄호 사이에는 공백이 필요하다.
- 매개변수는 가능하면 같은 줄에 정렬한다.
- 기본 들여쓰기는 2칸이다.
- 줄 바꿈한 매개변수는 4칸 들여쓰기한다.
미사용 매개변수
명확한 경우 이름을 생략할 수 있다.
class Foo {
public:
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
};
명확하지 않은 경우 함수 정의에서 매개변수 이름을 주석으로 표시한다.
class Shape {
public:
virtual void Rotate(double radians) = 0;
};
class Circle : public Shape {
public:
void Rotate(double radians) override;
};
void Circle::Rotate(double /*radians*/) {}
// 잘못된 예 - 나중에 구현하려는 경우, 매개변수의 의미가 불명확하다.
void Circle::Rotate(double) {}
속성(Attribute) 및 매크로
속성 및 속성으로 확장되는 매크로는 반환 타입 앞에 위치한다.
ABSL_ATTRIBUTE_NOINLINE void ExpensiveFunction();
[[nodiscard]] bool IsOk();
11.5 람다 표현식 (Lambda Expressions)
람다 표현식은 매개변수와 본문을 일반 함수처럼 포맷하고, 캡처 목록은 다른 콤마로 구분된 목록처럼 정렬한다.
참고 사항
- 참조로 캡처할 경우, 앰퍼샌드(&)와 변수 이름 사이에 공백을 넣지 않는다.
int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; }
짧은 람다는 인라인으로 함수 인자로 작성할 수 있다.
absl::flat_hash_set<int> to_remove = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&to_remove](int i) {
return to_remove.contains(i);
}),
digits.end());
람다 표현식은 코드 가독성을 높이는 데 유용하며, 특히 짧은 람다는 인라인으로 사용하여 간결하게 표현할 수 있다.
11.6 부동소수점 리터럴 (Floating-point Literals)
부동소수점 리터럴은 항상 소수점이 있어야 하며, 소수점 양쪽에 숫자가 있어야 한다. 이렇게 작성하면 정수 리터럴로 오해되는 것을 방지하고, 지수 표기법의 E/e가 16진수 숫자로 오해되는 것을 피할 수 있다.
부동소수점 변수는 정수 리터럴로 초기화할 수 있다(변수 타입이 그 정수를 정확히 표현할 수 있는 경우). 단, 지수 표기법을 사용할 경우 이는 정수 리터럴로 간주되지 않는다는 점에 유의한다.
float f = 1.0f; // 올바른 형식
float f2 = 1.0; // 가능하지만 f 접미사가 더 명확함
float f3 = 1; // 정수 리터럴로 초기화도 가능
long double ld = -0.5L; // 올바른 형식
double d = 1248.0e6; // 올바른 지수 표기법
이와 같은 표준 형식을 따르면 코드 가독성을 높일 수 있다.
11.7 함수 호출 형식 (Function Calls)
함수 호출 시 가독성을 위해 한 줄에 작성하거나, 인수를 괄호 내에서 적절히 나눠서 쓴다. 일반적으로 한 줄에 모든 인수가 들어갈 경우 한 줄로 작성하되, 여러 줄로 나눌 경우 아래 형식을 따른다.
bool result = DoSomething(argument1, argument2, argument3);
여러 줄에 나누어 작성할 때
모든 인수가 한 줄에 다 들어가지 않을 경우, 다음 줄부터는 첫 번째 인수와 같은 위치에 맞춰서 정렬한다. 괄호 뒤에 공백을 추가하지 않고, 닫는 괄호 앞에도 공백을 추가하지 않는다.
bool result = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
또는 인수를 네 칸 들여쓰기로 나눠서 작성할 수 있다.
if (...) {
bool result = DoSomething(
argument1, argument2, // 4칸 들여쓰기
argument3, argument4);
}
가독성을 위한 줄 나누기
- 가독성을 해치지 않는 범위에서 한 줄에 여러 인수를 배치해 줄 수를 줄이는 것을 우선한다.
- 모든 인수를 각각의 줄에 배치하는 것은 피하고, 특별한 경우에만 필요에 따라 구분한다.
- 특정 인수가 복잡하거나 가독성이 떨어질 경우, 그 인수를 설명하는 변수를 만들어 사용하거나, 해당 인수만 주석과 함께 별도 줄에 배치할 수 있다.
int my_heuristic = scores[x] * y + bases[x];
bool result = DoSomething(my_heuristic, x, y, z);
// 또는
bool result = DoSomething(scores[x] * y + bases[x], // Score heuristic.
x, y, z);
구조적으로 중요한 경우
인수가 의미 있는 구조를 가지는 경우, 구조에 따라 정렬하여 가독성을 높인다.
// 위젯을 3x3 행렬로 변환합니다.
my_widget.Transform(x1, x2, x3,
y1, y2, y3,
z1, z2, z3);
이 형식을 따름으로써 코드의 가독성과 유지보수성을 높일 수 있다.
11.8 중괄호 초기화 리스트 형식 (Braced Initializer List Format)
중괄호 초기화 리스트는 마치 함수 호출처럼 형식화한다. 즉, 중괄호 {} 가 괄호처럼 기능한다고 가정하여 함수 호출에 맞춰 정렬하는 방식이다.
중괄호 리스트가 이름 뒤에 붙는 경우(예: 타입이나 변수명)에는 {}가 해당 이름의 함수 호출처럼 보이도록 맞춘다. 이름이 없는 경우에는 길이가 0인 이름이 있다고 가정하고 형식을 맞춘다.
예시: 한 줄에 작성하는 경우
- 이름이 있거나 없는 중괄호 초기화 리스트 모두 한 줄에 작성할 수 있다면 다음과 같이 쓴다.
return {foo, bar};
functioncall({foo, bar});
std::pair<int, int> p{foo, bar};
예시: 여러 줄로 나누어야 하는 경우
- 한 줄에 모든 인수가 들어가지 않을 때는 함수 호출 형식처럼 나누어 정렬한다.
SomeFunction(
{"assume a zero-length name before {"},
some_other_function_parameter);
- 복잡한 구조의 경우, 각 요소를 구조적 의미에 맞게 들여쓰기를 하여 가독성을 높인다.
SomeType variable{
some, other, values,
{"assume a zero-length name before {"},
SomeOtherType{
"Very long string requiring the surrounding breaks.",
some, other, values},
SomeOtherType{"Slightly shorter string",
some, other, values}};
- 이름이 있는 타입 초기화 시 길이가 길어서 여러 줄로 나눠야 할 경우.
SomeType variable{
"This is too long to fit all in one line"};
- 여러 줄로 나눌 때, 중괄호 앞에서 줄을 바꿀 수 있다.
MyType m = { // Here, you could also break before {.
superlongvariablename1,
superlongvariablename2,
{short, interior, list},
{interiorwrappinglist,
interiorwrappinglist2}};
이와 같은 형식화 규칙을 통해 코드의 가독성과 구조를 유지하며 초기화 리스트를 효과적으로 활용할 수 있다.
11.9 반복 및 분기 구문 형식 (Looping and Branching Statements)
루프와 분기문은 다음과 같은 구성 요소로 이루어져 있다:
- 하나 이상의 문 키워드(예: if, else, switch, while, do, for).
- 괄호 안에 위치한 조건 또는 반복 지정자.
- 하나 이상의 제어문 또는 제어문 블록.
코드 스타일 규칙
- 구성 요소 간 공백
- 각 구성 요소는 단일 공백으로 구분하며 줄 바꿈 없이 이어서 작성한다.
- 조건 및 반복 지정자 내 공백
- 세미콜론 뒤에는 단일 공백(또는 줄 바꿈)을 넣고, 단 닫는 괄호나 또 다른 세미콜론 앞에는 공백을 넣지 않는다.
- 조건이나 반복 지정자의 여는 괄호 뒤와 닫는 괄호 앞에는 공백을 두지 않는다.
- 제어문 블록 처리
- 모든 제어문은 중괄호 {}로 묶는다.
- 여는 중괄호 뒤와 닫는 중괄호 앞에는 한 줄씩 공백을 넣는다.
if (condition) { // 올바른 예 - 괄호 안 공백 없음, 여는 중괄호 앞 공백 있음.
DoOneThing(); // 올바른 예 - 두 칸 들여쓰기.
DoAnotherThing();
} else if (int a = f(); a != 3) { // 올바른 예 - 닫는 중괄호가 새 줄에 있고 else는 같은 줄에 있음.
DoAThirdThing(a);
} else {
DoNothing();
}
잘못된 예시
if(condition) {} // 잘못된 예 - `if` 뒤에 공백 없음.
else if ( condition ) {} // 잘못된 예 - 괄호와 조건 사이에 공백 있음.
else if (condition){} // 잘못된 예 - 여는 중괄호 앞에 공백 없음.
else if(condition){} // 잘못된 예 - 여러 공백 없음.
for (int a = f();a == 10) {} // 잘못된 예 - 세미콜론 뒤에 공백 없음.
모든 if ... else 문에는 중괄호를 사용하는 것을 권장하며, 다음과 같이 중괄호가 빠진 예시는 피해야 한다.
if (condition)
foo;
else {
bar;
}
예외 규칙
역사적 이유로, 전체 구문이 한 줄 또는 두 줄로 작성되는 경우 중괄호나 중괄호 내 줄 바꿈을 생략할 수 있다.
단, 여러 키워드가 포함된 구문(if ... else 또는 do ... while)에는 적용되지 않는다.
// 괜찮음 - 한 줄에 맞음.
if (x == kFoo) { return new Foo(); }
// 괜찮음 - 중괄호 생략 가능.
if (x == kFoo) return new Foo();
switch문에서의 case 블록
switch문에서 case 블록에는 중괄호 사용 여부는 선택 사항이며, 사용할 경우 다음과 같이 작성한다.
switch (var) {
case 0: { // 2칸 들여쓰기
Foo(); // 4칸 들여쓰기
break;
}
default: {
Bar();
}
}
빈 루프 처리
빈 루프에는 중괄호 {}를 사용하는 것이 좋으며, 세미콜론 하나만 사용하는 방식은 피해야 한다.
while (condition) {} // 올바른 예 - `{}` 사용
while (condition) continue; // 올바른 예 - `continue` 사용
while (condition); // 잘못된 예 - `do-while` 루프의 일부처럼 보임
11.10 포인터 및 참조 표현식 (Pointer and Reference Expressions)
포인터와 참조 표현식을 작성할 때 다음과 같은 규칙을 따른다.
- 점(.)이나 화살표(->) 주변에 공백을 넣지 않는다.
- 포인터 연산자(* 또는 &) 뒤에 공백을 넣지 않는다.
올바른 포인터 및 참조 표현식 예시
x = *p;
p = &x;
x = r.y;
x = r->y;
- 멤버에 접근할 때 점(.)이나 화살표(->) 주변에 공백을 넣지 않는다.
- 포인터 연산자 뒤에 공백을 두지 않는다.
포인터 또는 참조 변수의 선언 및 정의에서의 공백 위치
포인터나 참조를 선언할 때(* 또는 &) 공백 위치는 다음 두 가지 방식 중 하나를 선택할 수 있다. 일관성을 유지하는 것이 중요하며, 파일 내에서는 한 가지 방식으로 통일해야 한다.
// 공백이 앞에 있는 경우 - 허용
char *c;
const std::string &str;
int *GetPointer();
std::vector<char *>
// 공백이 뒤에 있는 경우 (또는 생략) - 허용
char* c;
const std::string& str;
int* GetPointer();
std::vector<char*> // '*'와 '>' 사이에 공백 없음
기존 파일을 수정할 때는 해당 파일의 기존 스타일을 따르는 것이 권장된다.
여러 변수 선언 시 주의사항
가독성을 위해 같은 선언에서 여러 변수를 선언할 수 있지만, 포인터나 참조 기호가 포함된 변수를 함께 선언하는 것은 허용되지 않는다. 이러한 방식은 오독의 가능성이 있다.
// 가독성을 위한 여러 변수 선언 - 허용
int x, y;
// 포인터 또는 참조 기호와 함께 사용하는 경우 - 허용되지 않음
int x, *y; // 허용되지 않음 - 여러 변수 선언에서 `*`나 `&` 사용 금지
int* x, *y; // 허용되지 않음 - 여러 변수 선언에서 `*`나 `&` 사용 금지; 공백 일관성 없음
char * c; // 잘못된 예 - '*'의 양쪽에 공백이 있음
const std::string & str; // 잘못된 예 - '&'의 양쪽에 공백이 있음
11.11 불리언 표현식 (Boolean Expressions)
불리언 표현식이 한 줄의 표준 길이를 초과할 경우, 줄을 나누는 방식에 일관성을 유지해야 한다.
다음 예시에서는 논리 AND 연산자가 각 줄의 끝에 위치해 있다.
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
...
}
위 예시처럼 코드가 줄바꿈될 때, && 논리 AND 연산자가 각 줄 끝에 위치하는 것이 구글 코드에서 더 일반적이다. 그러나 모든 연산자를 줄의 시작에 배치하는 방식도 허용된다.
가독성을 위한 괄호 사용
필요에 따라 추가 괄호를 사용하여 가독성을 높일 수 있다. 단, 과도한 괄호 사용은 피하는 것이 좋다.
연산자 스타일
항상 기호 연산자(&&, ~ 등)를 사용해야 하며, 단어 연산자(and, compl 등)를 사용하는 것은 지양한다.
11.12 반환 값 (Return Values)
반환 표현식을 불필요하게 괄호로 감싸지 않도록 한다.
return expr; 구문에서 괄호 사용은 x = expr;에서 사용하는 방식과 동일하게 사용해야 한다.
return result; // 간단한 경우에는 괄호를 사용하지 않는다.
// 복잡한 표현식의 가독성을 높이기 위해 괄호 사용이 가능하다.
return (some_long_condition &&
another_condition);
다음과 같은 형태는 잘못된 예시다.
return (value); // 변수 할당 시 `var = (value);`로 작성하지 않는 것처럼 괄호 사용을 지양.
return(result); // `return`은 함수가 아니다!
11.13 변수와 배열 초기화 (Variable and Array Initialization)
변수와 배열 초기화 시 =, (), {} 중 원하는 방식을 선택할 수 있다. 다음과 같은 형식들이 모두 올바른 초기화 방식이다.
int x = 3;
int x(3);
int x{3};
std::string name = "Some Name";
std::string name("Some Name");
std::string name{"Some Name"};
중괄호 초기화 시 주의사항
중괄호 초기화({...})를 사용할 때, std::initializer_list 생성자가 있는 타입에 주의해야 한다. 비어 있지 않은 중괄호 초기화는 가능한 경우 항상 std::initializer_list 생성자를 우선으로 호출한다. 반면, 빈 중괄호 {}는 특별히 기본 생성자를 호출하므로 혼동을 피하고 싶다면 중괄호 대신 괄호 ()를 사용하는 것이 좋다.
std::vector<int> v(100, 1); // 100개의 원소가 1로 초기화된 벡터
std::vector<int> v{100, 1}; // 두 개의 원소가 100과 1로 초기화된 벡터
중괄호 초기화와 정수형 타입
또한, 중괄호 {} 초기화는 정수형 타입의 축소 변환(narrowing)을 방지한다. 이는 특정 유형의 프로그래밍 오류를 방지하는 데 도움이 될 수 있다.
int pi(3.14); // 허용 - pi == 3
int pi{3.14}; // 컴파일 오류: 축소 변환
11.14 전처리기 지시문 (Preprocessor Directives)
전처리기 지시문을 시작하는 # 기호는 항상 줄의 시작 위치에 있어야 한다.
전처리기 지시문이 들여쓰기된 코드 블록 내에 있더라도, 지시문은 줄의 시작 위치에서 시작해야 한다.
// 올바른 예시 - 지시문이 줄의 시작에 위치
if (lopsided_score) {
#if DISASTER_PENDING // 올바름 - 줄의 시작에서 시작
DropEverything();
# if NOTIFY // OK - # 뒤에 공백이 있으나 필수는 아님
NotifyClient();
# endif
#endif
BackToNormal();
}
잘못된 예시는 다음과 같다.
// 잘못된 예시 - 들여쓰기된 지시문
if (lopsided_score) {
#if DISASTER_PENDING // 잘못됨! `#if`는 줄의 시작에 있어야 함
DropEverything();
#endif // 잘못됨! `#endif`도 들여쓰지 말아야 함
BackToNormal();
}
전처리기 지시문은 코드 가독성을 위해 항상 줄의 시작에 위치해야 한다.
11.15 클래스 형식 (Class Format)
클래스 정의는 public, protected, private 순서로 구분된 섹션을 가지고 있으며, 각 섹션의 키워드는 한 칸 들여쓴다.
기본 클래스 정의 형식은 다음과 같다.
class MyClass : public OtherClass {
public: // 한 칸 들여쓰기!
MyClass(); // 일반적인 두 칸 들여쓰기.
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {
}
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
private:
bool SomeInternalFunction();
int some_var_;
int some_other_var_;
};
주의할 사항
- 기반 클래스 이름은 서브클래스 이름과 같은 줄에 위치하며, 최대 80자 제한을 넘지 않도록 한다.
- public:, protected:, private
- 키워드는 한 칸 들여쓰기로 작성한다.
- 첫 번째 섹션을 제외하고, 각 키워드 앞에 빈 줄을 추가하는 것이 권장되나, 작은 클래스에서는 생략할 수 있다.
- 이들 키워드 뒤에는 빈 줄을 남기지 않는다.
- public 섹션을 먼저 작성하고, 그다음 protected, 마지막으로 private 섹션을 작성한다.
- 각 섹션 내 선언 순서에 대한 규칙은 선언 순서 섹션에서 확인할 수 있다.
11.16 생성자 초기화 리스트 (Constructor Initializer Lists)
생성자 초기화 리스트는 한 줄에 모두 작성하거나, 이후 줄에 4칸 들여쓰기로 정렬할 수 있다.
허용되는 초기화 리스트 형식은 다음과 같다.
// 모든 항목이 한 줄에 들어갈 때:
MyClass::MyClass(int var) : some_var_(var) {
DoSomething();
}
// 시그니처와 초기화 리스트가 한 줄에 들어가지 않을 경우,
// 콜론 앞에서 줄을 바꾸고 4칸 들여쓰기:
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) {
DoSomething();
}
// 초기화 리스트가 여러 줄일 경우, 각 멤버를 별도의 줄에 두고 정렬:
MyClass::MyClass(int var)
: some_var_(var), // 4칸 들여쓰기
some_other_var_(var + 1) { // 줄 맞춤
DoSomething();
}
// 다른 코드 블록과 마찬가지로, 여닫는 중괄호가 한 줄에 들어갈 수 있다면
// 같은 줄에 둘 수 있다.
MyClass::MyClass(int var)
: some_var_(var) {}
이 형식들을 통해 코드 가독성을 높이며, 초기화 리스트 작성 시 줄 맞춤과 들여쓰기를 일관되게 유지하는 것이 중요하다.
11.17 네임스페이스 형식 (Namespace Formatting)
네임스페이스 내의 내용은 들여쓰지 않는다.
네임스페이스는 추가적인 들여쓰기 레벨을 적용하지 않으며, 다음과 같이 작성한다.
namespace {
void foo() { // 올바른 예 - 네임스페이스 내부에 추가적인 들여쓰기 없음.
...
}
} // namespace
잘못된 예시
네임스페이스 내부에서 들여쓰기를 사용하는 것은 잘못된 방식이다.
namespace {
// 잘못된 예 - 네임스페이스 내에서 들여쓰기를 사용함.
void foo() {
...
}
} // namespace
네임스페이스 내부에서는 들여쓰기 없이 작성하여 코드의 일관성을 유지하는 것이 중요하다.
11.18 수평 공백 사용 규칙 (Horizontal Whitespace)
수평 공백의 사용은 위치에 따라 다르며, 절대로 줄 끝에 공백을 추가하지 않는다.
일반 규칙
int i = 0; // 줄 끝 주석 앞에는 항상 두 칸 공백을 둔다.
void f(bool b) { // 여는 중괄호 앞에는 항상 공백을 둔다.
...
int i = 0; // 세미콜론 앞에는 보통 공백을 두지 않는다.
// 중괄호 초기화 목록에서 중괄호 내부의 공백은 선택 사항이다.
// 공백을 사용할 경우 양쪽 모두에 넣는다.
int x[] = { 0 };
int x[] = {0};
// 상속과 초기화 리스트에서는 콜론 주변에 공백을 둔다.
class Foo : public Bar {
public:
// 인라인 함수 구현 시 중괄호와 구현 사이에 공백을 둔다.
Foo(int b) : Bar(), baz_(b) {} // 빈 중괄호 내부에는 공백을 두지 않는다.
void Reset() { baz_ = 0; } // 중괄호와 구현 사이에 공백을 둔다.
...
줄 끝 공백을 추가하면 병합 시 불필요한 작업이 발생할 수 있다. 줄 끝 공백을 새로 추가하지 말고, 이미 수정 중인 줄에서 공백을 발견할 경우 제거하거나 별도의 정리 작업에서 제거한다.
루프 및 조건문
if (b) { // 조건문과 루프에서 키워드 뒤에 공백을 둔다.
} else { // `else` 앞뒤에 공백을 둔다.
}
while (test) {} // 보통 괄호 내부에는 공백을 두지 않는다.
switch (i) {
for (int i = 0; i < 5; ++i) {
// 루프와 조건문에서 괄호 내부에 공백을 둘 수 있으나, 이는 드물다. 일관성을 유지하는 것이 중요하다.
switch ( i ) {
if ( test ) {
for ( int i = 0; i < 5; ++i ) {
// `for` 루프에서는 세미콜론 뒤에 항상 공백을 두고, 세미콜론 앞에 공백을 둘 수 있으나 이는 드물다.
for ( ; i < 5 ; ++i) {
...
// 범위 기반 `for` 루프에서는 콜론 앞뒤에 항상 공백을 둔다.
for (auto x : counts) {
...
}
switch (i) {
case 1: // `switch`의 `case`에서 콜론 앞에는 공백을 두지 않는다.
...
case 2: break; // 콜론 뒤에 코드가 있을 경우 공백을 둔다.
연산자
// 대입 연산자는 항상 양쪽에 공백을 둔다.
x = 0;
// 다른 이항 연산자도 보통 양쪽에 공백을 두지만, 피연산자 간 공백을 제거해도 괜찮다.
// 괄호 내부에는 공백을 두지 않는다.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// 단항 연산자와 피연산자 사이에는 공백을 두지 않는다.
x = -5;
++x;
if (x && !y)
...
템플릿과 형변환
// 꺾쇠 괄호(`<` 및 `>`) 내부, `<` 앞, 또는 형변환 `>(` 사이에는 공백을 두지 않는다.
std::vector<std::string> x;
y = static_cast<char*>(x);
// 타입과 포인터 사이의 공백은 허용되나, 일관성을 유지한다.
std::vector<char *> x;
11.19 수직 공백 사용 (Vertical Whitespace)
수직 공백의 사용은 최소화하는 것이 좋다.
이 원칙은 규칙보다는 가이드라인에 가깝다. 불필요한 빈 줄은 최대한 피하고, 함수 사이에는 한 줄 또는 두 줄 이상 공백을 넣지 않는다. 함수 시작과 끝에는 빈 줄을 두지 않는 것이 권장되며, 공백 사용을 신중하게 해야 한다. 코드 블록 내의 빈 줄은 글에서 문단 구분처럼 작용하여 두 가지 다른 사고를 시각적으로 분리해 준다.
기본 원칙은 한 화면에 더 많은 코드가 표시될수록 흐름을 이해하기 쉽다는 것이다. 수직 공백은 코드의 흐름을 구분하기 위해 목적에 맞게 사용해야 한다.
수직 공백을 사용할 때 참고할 가이드라인
- 함수 시작이나 끝에 빈 줄을 넣는 것은 가독성에 도움이 되지 않는다.
- if-else 블록 내에서 빈 줄을 넣으면 가독성이 향상될 수 있다.
- 주석 줄 앞에 빈 줄을 넣으면 가독성에 도움이 된다. 새로운 주석은 새로운 생각을 제시하므로, 그 다음 코드를 시각적으로 연결하기에 유리하다.
- 네임스페이스 선언 내부에 빈 줄을 넣으면, 중요 내용과 조직적 래퍼를 시각적으로 구분하여 가독성을 높일 수 있다. 특히 네임스페이스 내 첫 선언에 주석이 있는 경우, 이 빈 줄은 주석이 다음 선언에 "연결"되는 효과를 준다.
참조URL
https://google.github.io/styleguide/cppguide.html
'기술 노트 > Google C++ Style Guide' 카테고리의 다른 글
Google C++ Style Guide(2024) - 12장 규칙 예외(Exceptions to the Rules) (1) | 2024.11.15 |
---|---|
Google C++ Style Guide(2024) - 10장 주석(Comments) (0) | 2024.11.13 |
Google C++ Style Guide(2024) - 9장 명명 규칙(Naming) (1) | 2024.11.12 |
Google C++ Style Guide(2024) - 8장 포괄적 언어(Inclusive Language) (0) | 2024.11.11 |
Google C++ Style Guide(2024) - 7장 기타 C++ 기능(Other C++ Features) (6) | 2024.11.10 |