Formatting
모든 사람이 동일한 스타일을 사용하면 프로젝트를 훨씬 더 쉽게 따라갈 수 있다.
Line Length
한 줄의 길이는 최대 80자로 제한한다. (1960년대 기준)
하지만 다음과 같은 경우에는 80자를 초과할 수 있다.
- 긴 URL이나 명령어가 포함된 주석 (잘못 나누면 복사, 붙여넣기가 어려움)
- string literal 내의 중요한 데이터 (URL, JSON, SQL 등)
- #include 문, 헤더 가드, using 선언
사용성/검색성과 가독성 사이에서 균형을 맞춰야 한다.
Non-ASCII Characters
되도록 사용을 피하되 필요한 경우 UTF-8 인코딩을 사용한다.
단, char16_t, char32_t, wchar_t는 사용하지 않는다.
std::string greeting = u8"こんにちは"; // 일본어 "안녕하세요"
std::string bom = "\xEF\xBB\xBF"; // UTF-8 BOM(Byte Order Mark)
std::string text = "안녕하세요"; // ✅ UTF-8 인코딩 사용
char16_t text[] = u"안녕하세요"; // ❌ char16_t 사용 금지
wchar_t text[] = L"Hello"; // ❌ wchar_t 사용 금지
Spaces vs. Tabs
들여쓰기는 space 2칸을 사용하며, tab은 절대 사용하지 않는다.
- 에디터를 설정하여 Tab 키를 누르면 space가 삽입되도록 한다.
Function Declarations and Definitions
함수 선언과 정의는 가독성을 고려하여 적절히 정렬해야 한다.
- 반환 타입은 함수 이름과 같은 줄에 배치하는 것이 원칙이다.
- 매개변수가 많아 한 줄에 다 들어가지 않으면 줄을 나누어 정렬해야 한다.
- 예시 1. 매개변수가 많을 경우 줄 나누기
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
Type par_name3) {
DoSomething();
}
- 예시 2. 함수 이름이 너무 길 경우
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) {
DoSomething(); // 2 space indent
}
[[nodiscard]], ABSL_ATTRIBUTE_NOINLINE 같은 속성(Attribute)은 반환 타입 앞에 배치한다.
[[nodiscard]] bool IsValid();
ABSL_ATTRIBUTE_NOINLINE void ExpensiveFunction();
Lambda Expressions
람다의 매개변수와 본문은 일반 함수와 동일한 스타일을 적용해야 한다.
- 짧은 람다는 인라인으로 사용할 수 있다.
- 참조 캡쳐(&x)에는 공백을 두지 않는다.
int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; };
- 예시 1. 여러 줄 람다를 정렬
digits.erase(std::remove_if(digits.begin(), digits.end(),
[&to_remove](int i) {
return to_remove.contains(i);
}),
digits.end());
- 예시 2. 잘못된 예시 (들여쓰기 불일치)
digits.erase(std::remove_if(digits.begin(), digits.end(),
[&to_remove](int i) { return to_remove.contains(i); }),
digits.end()); // ❌ 너무 길어서 가독성이 떨어짐.
Floating-point Literals
부동소수점에는 항상 소수점(radix point)를 포함해야한다.
- 소수점 앞 뒤에는 항상 숫자가 있어야 함 (.5 ❌ → 0.5 ✅)
- 지수 표기법(Exponential Notation)에서도 소수점을 포함 (1e6 ❌ → 1.0e6 ✅)
- 정수를 부동소수점 변수에 할당하는 것은 가능하지만, 명확성을 위해 .0을 추가하는 것이 좋음
- float, double, long double 타입에 맞는 접미사(f, L)를 사용
Function Calls
가능한 경우 함수 호출은 한 줄에 작성한다.
bool result = DoSomething(argument1, argument2, argument3); // ✅
bool result = DoSomething(
argument1, argument2, argument3); // ❌ 불필요한 줄 나누기
모든 인자가 한 줄에 들어가지 않을 경우, 첫 번째 인자와 정렬하거나 4칸 들여쓰기 한다.
// 첫 번째 인자와 정렬
bool result = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
// 4칸 들여쓰기
bool result = DoSomething(
argument1, argument2, // 4 space indent
argument3, argument4);
여는 괄호 ( 뒤와 닫는 괄호 ) 앞에는 공백을 추가하지 않는다.
가독성을 높이기 위해 복잡한 인자는 별도의 변수에 할당할 수도 있다.
bool result = DoSomething(scores[x] * y + bases[x], x, y, z); // ❌ 가독성 낮음
// ✅ 1. 변수로 정리
int my_heuristic = scores[x] * y + bases[x];
bool result = DoSomething(my_heuristic, x, y, z);
// ✅ 2. 주석으로 의미 설명
bool result = DoSomething(scores[x] * y + bases[x], // Score heuristic.
x, y, z);
행렬과 같이 논리적인 그룹이 있는 경우, 그 구조를 유지하면서 정렬할 수 있다.
// Transform the widget by a 3×3 matrix.
my_widget.Transform(x1, x2, x3,
y1, y2, y3,
z1, z2, z3);
Braced Initializer List Format
중괄호 {} 초기화는 함수 호출과 동일한 스타일을 적용해야 한다.
한 줄에 표현할 수 있으면 한 줄로 유지한다.
return {foo, bar};
functioncall({foo, bar});
std::pair<int, int> p{foo, bar};
여러 줄로 나누어야 할 경우, 첫 번째 요소와 정렬하거나 4칸 들여쓰기 한다.
// When you have to wrap.
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"};
중첩된 리스트는 논리적인 그룹을 유지하면서 정렬해야 한다.
명확성을 위해 필요하면 중괄호 {}를 함수 호출처럼 정렬한다.
Looping and branching statements
if, else, switch, while, do, for 등의 키워드는 띄어쓰기를 포함해야 한다.
조건문 또는 반복문의 괄호 ( ) 내부에는 공백을 추가하지 않는다.
중괄호 {}는 항상 사용해야 하며, 열리는 { 뒤와 닫히는 } 앞에는 한 줄씩 추가한다.
- 예시 1. 올바른 예시
if (condition) { // ✅ 공백 형식 유지
DoOneThing();
DoAnotherThing();
} else if (int a = f(); a != 3) { // ✅ 초기화된 변수 포함 가능
DoAThirdThing(a);
} else {
DoNothing();
}
// Good - the same rules apply to loops.
while (condition) {
RepeatAThing();
}
// Good - the same rules apply to loops.
do {
RepeatAThing();
} while (condition);
// Good - the same rules apply to loops.
for (int i = 0; i < 10; ++i) {
RepeatAThing();
}
- 예시 2. 잘못된 예시
if(condition) {} // ❌ `if` 뒤에 공백 없음
else if ( condition ) {} // ❌ 괄호 안쪽에 공백이 포함됨
else if(condition){} // ❌ 여러 곳에서 공백 누락
if (condition)
foo; // ❌ 중괄호 `{}` 없으면 가독성이 떨어지고, 버그 가능성 증가
else {
bar;
}
// Bad - `if` statement too long to omit braces.
if (condition)
// Comment
DoSomething();
// Bad - `if` statement too long to omit braces.
if (condition1 &&
condition2)
DoSomething();
if ... else, do ... while 문은 한 줄로 축약할 수 없다.
- 간결한 경우 예외적으로 한줄로 표현 가능
if (x == kFoo) return new Foo(); // ✅ 짧고 간결한 경우만 허용
if (x == kBar)
Bar(arg1, arg2, arg3); // ✅ 한 줄 조건문, 다음 줄 실행문 가능
switch 문에서 case 블록은 {}를 포함할 수도 있고 생략할 수도 있다.
빈 루프(Empty Loop)는 {} 또는 continue;를 사용해야 한다.
while (condition) {} // Good - `{}` indicates no logic.
while (condition) {
// Comments are okay, too
}
while (condition) continue; // Good - `continue` indicates no logic.
Pointer and Reference Expressions
멤버 접근 연산자(. 및 →) 주변에는 공백을 추가하지 않는다.
포인터(*), 참조(&)연산자는 뒤에 공백을 두지 않는다.
x = *p;
p = &x;
x = r.y;
x = r->y;
포인터 및 참조 선언에서 * 및 & 의 위치는 앞 또는 뒤에 둘 수 있다.
- 일관성을 유지하는게 중요함
같은 선언 문에서 포인터/참조 변수와 일반 변수를 함께 선언하지 않는다.
int x, *y; // ❌ 일반 변수와 포인터 변수 혼합 선언 금지
int* x, *y; // ❌ `*`가 변수별로 다르게 적용되어 가독성 저하
템플릿에서 *> 처럼 * 뒤 공백을 생략해야 한다.
// These are fine, space preceding.
char *c;
const std::string &str;
int *GetPointer();
std::vector<char *>
// These are fine, space following (or elided).
char* c;
const std::string& str;
int* GetPointer();
std::vector<char*> // Note no space between '*' and '>'
Boolean Expressions
긴 표현식을 줄바꿈할 때 연산자(&&, ||)의 위치를 일관되게 유지해야 한다.
연산자는 일반적으로 줄 끝에 배치하지만, 줄 시작에 배치하는 것도 허용된다.
불필요한 괄호 사용을 피하면서도, 가독성을 높이기 위해 적절한 괄호를 추가할 수 있다.
&&, || 등의 연산자는 and, or 같은 키워드 대신 기호(&&, ||)를 사용한다.
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
DoSomething();
}
if (this_one_thing > this_other_thing
&& a_third_thing == a_fourth_thing
&& yet_another && last_one) { // ❌ 연산자가 줄 시작에 배치됨 -> 가독성 하락
DoSomething();
}
Return Values
단순한 return 표현식에서는 불필요한 괄호를 사용하지 않는다.
return 문에서 괄호는 복잡한 표현식을 명확하게 하는 경우에만 사용한다.
return (some_long_condition &&
another_condition);
return은 함수가 아니므로 return(result); 같은 표현은 잘못된 스타일이다.
- return(result); ❌ → return result; ✅
Variable and Array Initialization
변수 초기화 시 =, (), {} 세 가지 스타일을 선택할 수 있다.
std::initializer_list 생성자가 있는 경우 {} 초기화 방식이 예상과 다르게 동작할 수 있다.
std::vector<int> v(100, 1); // ✅ 100개의 요소가 1로 채워진 벡터
std::vector<int> v{100, 1}; // ✅ 2개의 요소를 가진 벡터 (100, 1)
std::vector<int> v{100}; // ❌ 벡터 크기 100을 의도했으나, 원소가 1개(100)만 있음
{} 초기화 방식은 암시적 변환(narrowing conversion)을 방지한다.
int pi(3.14); // ✅ OK, pi == 3
int pi = 3.14; // ✅ OK, pi == 3
int pi{3.14}; // ❌ 컴파일 오류: narrowing conversion 금지
기본 생성자를 호출하려면 {} 또는 ()를 사용할 수 있다.
std::vector<int> v1; // ✅ 기본 생성자 호출 (빈 벡터)
std::vector<int> v2{}; // ✅ 기본 생성자 호출 (빈 벡터)
std::vector<int> v3(); // ❌ 함수 선언으로 해석됨 (Most Vexing Parse)
Preprocessor Directives
#include, #define, #ifdef, #endif 등 반드시 줄의 시작 부분에서 시작해야 한다.
코드 블록 내에 있어도 전처리기 지시문은 절대 들여쓰기(indentation)하지 않는다.
# 뒤에는 공백을 넣을 수도 있지만, 일반적으로 넣지 않는 것이 권장된다.
#if ~ #endif 블록을 사용할 때는 일관된 스타일을 유지해야 한다.
if (lopsided_score) {
#if DISASTER_PENDING // ✅ 줄의 시작에서 시작
DropEverything();
# if NOTIFY // ✅ `#` 뒤에 공백을 넣어도 되지만 권장되진 않음
NotifyClient();
# endif
#endif
BackToNormal();
}
if (lopsided_score) {
#if DISASTER_PENDING // ❌ `#if`가 들여쓰기 됨
DropEverything();
#endif // ❌ `#endif`도 들여쓰기 됨
BackToNormal();
}
Class Format
public:, protected:, private: 키워드는 한 칸 들여쓰기한다.
public: → protected: → private: 순서로 배치한다.
public:, protected:, private: 키워드 앞에 빈 줄을 추가할 수 있지만, 첫 번째는 제외한다.
public: 이후 빈 줄을 두지 않는다.
기본적인 들여쓰기는 2칸을 사용한다.
class MyClass : public OtherClass {
public: // Note the 1 space indent!
MyClass(); // Regular 2 space indent.
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 순서를 유지
class DerivedClass : private BaseClass, public AnotherBase { // ❌ `public`이 먼저 와야 함
public:
void SomeFunction();
};
Constructor Initializer Lists
모든 초기화 목록이 한 줄에 들어가면 그대로 유지한다.
MyClass::MyClass(int var) : some_var_(var) { // ✅ 올바른 예시 (한 줄 유지)
DoSomething();
}
MyClass::MyClass(int var)
: some_var_(var) { // ❌ 한 줄에 충분히 들어가므로 줄바꿈 불필요
DoSomething();
}
여러 줄로 나눠야 할 경우, :(콜론) 앞에서 줄바꿈하고 4칸 들여쓰기한다.
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) { // ✅ 올바른 예시 (줄바꿈 후 4칸 들여쓰기)
DoSomething();
}
MyClass::MyClass(int var) :
some_var_(var), some_other_var_(var + 1) { // ❌ `:` 뒤에서 줄바꿈 금지
DoSomething();
}
여러 개의 멤버를 초기화하는 경우, 각 멤버를 개별 줄에 배치하고 정렬한다.
중괄호 {}는 한 줄에 배치할 수도 있고, 새로운 줄에 배치할 수도 있다.
- 중괄호 {}는 :과 동일한 줄에 위치해야 함
MyClass::MyClass(int var) : some_var_(var) {}
MyClass::MyClass(int var)
: some_var_(var)
{
DoSomething();
} // ❌ `{`의 위치가 잘못됨
Namespace Formatting
네임스페이스 내부의 코드(함수, 클래스, 변수 등)는 들여쓰기하지 않는다.
namespace {
void foo() { // ✅ 네임스페이스 내부지만 추가 들여쓰기 없음
DoSomething();
}
} // namespace
네임스페이스 {}는 일반적인 코드 블록처럼 취급하지만, 내부 내용은 평소대로 들여쓴다.
namespace my_namespace {
void foo() {
if (condition) {
DoSomething();
}
}
} // namespace my_namespace
여러 개의 네임스페이스가 중첩될 경우, 들여쓰기 없이 같은 규칙을 적용한다.
namespace outer_namespace {
namespace inner_namespace {
void foo() {
DoSomething();
}
} // namespace inner_namespace
} // namespace outer_namespace
Horizontal Whitespace
한 줄 끝에는 공백(trailing whitespace)을 추가하지 않는다.
// 주석 앞에는 공백 두 칸을 추가한다.
int i = 0; // ✅ 공백 두 칸 유지
int i = 0;// ❌ 공백이 없음
int i = 0; // ❌ 공백 하나만 있음
{} 중괄호 앞에는 공백을 추가한다.
void f(bool b) { // ✅ `{` 앞에 공백 포함
...
}
void f(bool b){ // ❌ `{` 앞에 공백이 없음
...
}
int x[] = { 0 }; // ✅ `{}` 내부 공백을 넣을 수도 있음
int x[] = {0}; // ✅ `{}` 내부 공백을 제거할 수도 있음
:(콜론)은 상속 및 초기화 목록에서 공백을 포함해야 한다.
class Foo : public Bar { // ✅ `:` 앞뒤에 공백 포함
};
Foo(int b) : Bar(), baz_(b) {} // ✅ `:` 앞뒤에 공백 포함
class Foo:public Bar { // ❌ `:` 앞뒤에 공백 없음
};
if, while, for 등의 키워드 뒤에는 공백을 추가한다.
if (b) { // ✅ `if` 뒤에 공백 포함
...
}
while (test) { // ✅ `while` 뒤에 공백 포함
...
}
for (int i = 0; i < 5; ++i) { // ✅ `for` 뒤에 공백 포함
...
}
for (auto x : counts) { // ✅ `:` 앞뒤 공백 포함
...
}
if(b) { // ❌ `if` 뒤에 공백 없음
...
}
if (b) { // ❌ `if` 뒤에 공백이 두 개 이상 있음
...
}
연산자(=, +, -, *, /)는 일반적으로 공백을 포함하지만, 곱셈과 나눗셈은 예외 가능하다.
x = 0; // ✅ `=` 연산자 양쪽 공백 포함
v = w * x + y / z; // ✅ `*`, `/` 연산자는 생략 가능
v = w*x + y/z; // ✅ `*`, `/` 연산자 붙여도 허용됨
x=0; // ❌ 공백 없음
템플릿 및 캐스트 연산에서 < > 사이에는 공백을 넣지 않는다.
std::vector<std::string> x; // ✅ `< >` 내부 공백 없음
std::vector< std::string > x; // ❌ `< >` 내부 공백 있음
y = static_cast<char*>(x); // ✅ `>(` 사이 공백 없음
y = static_cast<char*> (x); // ❌ `>(` 사이 공백 포함됨
Vertical Whitespace
불필요한 빈 줄(blank lines)은 최소화해야 한다. (필요한 곳에서만 사용)
함수 시작과 끝에 빈 줄을 추가하지 않는다.
if-else 블록 내부에서는 가독성을 위해 빈 줄을 적절히 사용할 수 있다.
주석 앞에는 빈 줄을 추가하여 주석이 뒤의 코드와 연결되도록 한다.
DoSomething();
// 다음 단계에서 값을 초기화한다.
InitializeValues();
네임스페이스 선언 내부에서 첫 번째 선언과 구분을 위해 빈 줄을 추가할 수 있다.
namespace my_namespace {
// 첫 번째 함수 선언
void Foo();
void Bar();
} // namespace my_namespace
'CSE > 알쓸모' 카테고리의 다른 글
Google C++ Style Guide 정리하기 - Comments (0) | 2025.03.04 |
---|---|
Google C++ Style Guide 정리하기 - Naming (0) | 2025.03.03 |
Google C++ Style Guide 정리하기 - Functions (0) | 2025.03.02 |
Google C++ Style Guide 정리하기 - Classes (2) | 2025.03.01 |
어도비(adobe) 조기 취소 수수료 안내고 싶은데. (0) | 2025.02.28 |