코딩 스타일 지침서

1. 목적

1.1. 필요성

코딩 컨벤션(convension)을 준수하면 소스 코드의 일관성(consistency) 이 유지되어 가독성(readability)을 높여주고 유지 보수를 용이하게 한다. 또, 나쁜 코딩 스타일을 피하여 실수로 인한 버그 발생을 감소시켜 소프트웨어 품질을 향상시킨다.

1.2. 주요 사항

C++은 C에서 C with class, Template Programming, Generic Programming, Metaprogramming, functional style programming 등으로 점차 진화, 발전되었다. 하지만 generic, metaprogramming, functional 등 일부 추가된 기술은 가독성을 떨어뜨리는 경향이 있으므로 선별적으로 사용한다.

표준(C++03~C++20)의 기능을 사용하며 C++17, C++20의 기능을 프로젝트에 사용할 때는 이식성을 고려해야 한다.

처음 세 개 절인 포맷팅, 네이밍, 주석은 스타일 위주로 정의된 반면, 다음 절부터는 C++ 프로그램 구조에 영향을 주는 규칙이 포함되었다.

2. 포맷팅 가이드

2.1. 포맷팅 (Formatting)

2.1.1. 라인길이

한 라인에 최대 100글자를 넘지 않도록 한다.
(길수록 가독성이 떨어짐 vs. 모니터의 대형화로 길게 해도 무방)

2.1.2. Space vs. Tab

들여쓰기는 공백(Space) 4개 길이와 동등하게 설정된 탭(Tab)을 사용한다.

2.1.3. 함수 선언과 정의

ReturnType ClassName::FunctionName(Type par_name1, Type par_name2)
{
    DoSomething();
    ...
}
  1. 리턴타입은 함수명과 같은 라인에 있어야 한다.

  2. 함수명과 ‘(‘ 사이, 파라미터와 ‘(‘, ‘)’ 사이에는 공백이 없다.

  3. { 과 } 는 단독으로 한 줄을 사용한다.

  4. 모든 파라미터는 이름이 있어야 하며 선언과 구현의 파라미터명이 동일해야 한다.

ReturnType LongClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
                                                 Type par_name3)
{
    DoSomething(); // 1 tab indent
    ...
}
  1. 리턴타입, 함수명, 파라미터를 한 줄로 쓰기에 길면 파라미터 일부를 다음 줄에 적는다.

  2. 새로운 줄의 파라미터는 첫 번째 파라미터를 기준으로 정렬한다.

2.1.4. 함수 호출

bool retval = DoSomething(argument1, argument2, argument3);

if (...)
{
    bool retval = DoSomethingThatRequiresALongFunctionName(
        very_long_argument1, // 1 tab indent
        argument2,
        argument3,
        argument4);
}

함수명과 파라미터를 한 줄로 쓰기에 길면 파라미터를 일부를 다음 줄에 적는다. 들여쓰기와 정렬방식은 ‘함수 선언과 정의’의 내용과 같다.

2.1.5. 조건문

if (condition) // no spaces inside parentheses
{
    ... // 1 tab indent.
}
else if (...)
{
    ...
}
else
{
    ...
}

if (condition)
{
    DoSomething(); // 1 tab indent.
}
  1. 괄호안에 공백이 없어야 하며 else 키워드는 새 줄에 적는다.

  2. if 와 괄호사이에 공백1개가 있어야 한다.

  3. { 과 } 는 단독으로 한 줄을 사용한다.

if (condition)
{
    DoSomething(); // 1 tab indent.
}
if (x == kFoo)
{
    return new Foo();
}

2.1.6. 루프와 Switch문

switch (var)
{
case 0:
{
    ...
    break;
}
case 1:
{
    ...
    break;
}
default:
{
    assert(false);
}
}

switch (var)
{
case 0:
    ...
    break;
case 1:
    ...
    break;
default:
    assert(false);
}
  1. switch문의 case에 {, }를 사용하는 경우 {, }는 단독으로 한 줄을 사용한다.

while (condition)
{
}

for (int i = 0; i < kSomeNumber; ++i)
{
}

while (condition)
{
    continue;
}
  1. 조건문만 있고 몸체가 없는 loop인 경우 { } 또는 continue 문을 사용한다.

2.1.7. 포인터와 참조 표현식

x = *p;
p = &x;
x = r.y;
x = r->y;

// These are fine, space preceding.
char *c;
const string &str;

화살표(→)와 점(.) 앞뒤에 공백을 넣지 않는다. 포인터연산자(*, &) 다음에도 공백을 넣지 않는다. 선언문의 *, & 다음에도 공백을 넣지 않는다.

2.1.8. 표현식

if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one)
{
    ...
}

조건문의 boolean 표현식(논리식)이 길어서 한 줄로 쓰기에 길면 위와 같은 형식으로 줄을 바꾼다. 가독성을 높이기 위해 ‘(‘, ‘)’를 적절히 사용한다.

2.1.9. 리턴 값

return result;
return (some_long_condition &&
        another_condition);

리턴값(return expr; 의 expr)을 ‘(‘, ‘)’로 묶지 않는다. 단, 가독성이 향상되는 경우에는 괄호를 쓸 수 있다.

2.1.10. 변수와 배열 초기화

int x = 3;
int x(3);
string name("Some Name");
string name = "Some Name";

변수 초기화를 위해 = 와 ( )를 사용할 수 있다.

2.1.11. 프리프로세서 지시어

// Good - directives at beginning of line
    if (lopsided_score)
    {
#if DISASTER_PENDING // Correct -- Starts at beginning of line
        DropEverything();
# if NOTIFY // OK but not required -- Spaces after #
        NotifyClient();
# endif
#endif
        BackToNormal();
    }

프리프로세서 지시어는 들여쓰기를 하지 않고 항상 첫 번째 열에서 시작한다.

2.1.12. 생성자 초기화 리스트

// When it all fits on one line:
MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1)
{
}

// When it requires multiple lines, indent 2 tab, putting the colon on
// the first initializer line:
MyClass::MyClass(int var)
        : some_var_(var), // 2 tab indent
          some_other_var_(var + 1) // 2 tab indent + 2 space
{
    ...
    DoSomething();
    ...
}

생성자의 초기화 리스트는 위와 같은 포맷으로 한 줄에 쓰거나 여러 줄에 나누어 쓴다.

2.1.13. 네임스페이스 포맷팅

namespace foo {
namespace bar {

void foo() // Correct. No extra indentation within namespace
{
    ...
}

} // namespace bar
} // namespace foo

네임스페이스는 시작 괄호‘{‘를 namespace 키워드와 같은 줄에 쓰며, 들여쓰기 수준을 증가 시키지 않는다.

2.1.14. 가로 여백 (Horizental Whitespace)

int i = 0; // Semicolons usually have no space before them.
int x[] = {0};
// Spaces around the colon in inheritance and initializer lists.
class Foo : public Bar
{
public:
    // For inline function implementations, put spaces between the braces
    // and the implementation itself.
    Foo(int b) : Bar(), baz_(b) {} // No spaces inside empty braces.
    void Reset() { baz_ = 0; } // Spaces separating braces from implementation.
}
}
  1. 일반적으로 세미콜론 ‘;’ 앞에는 공백을 두지 않는다.

  2. 배열 초기화인 경우 초기화 데이터와 ‘{‘, ‘}’ 사이에 공백을 넣지 않는다.

  3. 함수 정의를 1줄로 작성 할 때는 구현코드와 ‘{‘, ‘}’사이에 공백을 넣는다.

  4. 빈 괄호 ( ), { } 안에도 공백을 넣지 않는다.

while (test) // There is usually no space inside parentheses.
switch (i)
for (int i = 0; i < 5; ++i)
for (; i < 5; ++i) // For loops always have a space after the
                   // semicolon.
switch (i)
case 1: // No space before colon in a switch case.
        ...
case 2: break; // Use a space after a colon if there's code after it.
  1. while, switch, for의 괄호 (, ) 는 안쪽에 공백을 넣지 않고 붙여 쓴다.

  2. for문의 세미콜론(;) 다음에는 항상 공백을 넣는다.

  3. case문의 콜론(:) 앞에는 공백을 두지 않는다. 콜론(:) 다음에는 공백을 넣는다.

x = 0; // Assignment operators always have spaces around
       // them.
x = -5; // No spaces separating unary operators and their
++x; // arguments.
if (x && !y)
    ...
v = w * x + y / z; // Binary operators usually have spaces around them,
v = w*x + y/z; // but it's okay to remove spaces around factors.
v = w * (x + z); // Parentheses should have no spaces inside them.
  1. 대입 연산자 (=) 앞뒤로 공백을 넣는다.

  2. 일항 연산자(unary operator; -, ++, !)는 공백없이 붙여 쓴다.

  3. 이항 연산자는 앞뒤로 공백을 넣는다. 단, *,/ 는 공백없이 쓸 수 있다.

  4. 연산자 괄호 (, ) 도 안쪽에 공백을 넣지 않고 붙여 쓴다.

vector<string> x; // No spaces inside the angle
y = static_cast<char*>(x); // brackets (< and >), before
                           // <, or between >( in a cast.
  1. 템플릿 괄호 <, > 안쪽에 공백을 넣지 않고 붙여 쓴다.

  2. 템플릿 괄호 <, > 전후로도 붙여 쓴다.

2.1.15. 세로 여백 (Vertical Whitespace)

화면상에 코드가 많이 보일수록 제어흐름을 따라가고 이해하기 쉬우므로 코드상의 빈 줄 삽입을 최소화 한다. 중첩된 if-else내의 빈 줄은 가독성에 도움을 주지만 함수 시작과 끝부분의 빈 줄은 별로 도움이 안된다.

  1. 함수 정의 사이의 빈 줄은 2줄을 초과해서 넣지 않는다.

  2. 함수 정의 시작과 끝 부분에 빈 줄을 넣지 않는다.

2.2. 주석 (Comments)

2.2.1. 일반

  1. /* */, /** */ 보다는 가급적 //, /// 를 사용한다. ( ///, /** */는 doxygen처리용)

  2. 주석내용은 서술형으로 입력한다.

2.2.2. 파일 주석

//=============================================================================
//
// Copyright (c) 2010-2012, JS Solution All Rights Reserved.
//
// No part of this program may be copied, reproduced, stored in a
// retrieval system, or transmitted in any form or by any means
// - electronic, mechanical, photocopying, recording, or otherwise -
// without written permission of the licensee, JS Solution
//
//==============================================================================
///
/// @file hdsr.cpp
/// @brief hdsr process
///
/// 실행중인 서버가 Master인 경우 HDSR버퍼에 저장된 포인트 값을 읽어서 이력데이터파일로
/// 저장하고 Hdsr Sync를 통해 Slave에게 포인트 값을 보낸다. 실행중인 서버가 Slave인 경우
/// Hdsr Sync를 통해 Master로부터 포인트 값을 전달받아 이력데이터파일로 저장한다.
///
/// @author ws.woo
/// @date 2011-10-05
///
  1. 모든 .h, .cpp파일은 저작권 안내(저작권 표시, 라이선스문구, 작성자)로 시작 한다. 그 다음에 파일의 내용을 기술하는 주석을 쓴다.

  2. 일반적으로 .h 에는 클래스가 무엇인지, 어떻게 사용하는지에 대해 기술하는 주석을 쓰고, .cpp에는 상세 구현에 대한 자세한 정보를 쓴다.

  3. .h와 .cpp에 같은 내용의 주석을 중복해서 쓰지 않는다.

2.2.3. 클래스 주석

///
/// @brief 여기에 클래스에 대한 설명을 적는다.
///
/// 클래스에 대한 자세한 설명
///
/// 사용 예:
/// @verbatim
/// GargantuanTableIterator* iter = table->NewIterator();
/// for (iter->Seek("foo"); !iter->done(); iter->Next())
/// {
///     process(iter->key(), iter->value());
/// }
/// delete iter;
/// @endverbatim
///
class GargantuanTableIterator
{
    ...
};

모든 클래스 정의에는 그 클래스가 무엇인지 어떻게 사용하는지를 기술하는 주석을 써야한다.

2.2.4. 함수 주석

class MyClass
{
public:
///
/// @brief 함수에 대한 간단한 설명
/// 함수에 대한 간단한 설명 좀 더
///
/// 함수에 대한 자세한 설명
/// 기타 자세한 설명
///
/// @param [in] param1
/// param1에 대한 설명
/// @param [in,out] param2
/// param2에 대한 설명
/// @param [out] param3
/// param3에 대한 설명
///
/// @return 출력값에 대한 설명
/// @retval 1 성공
/// @retval 0 실패
int Foo(int value);
}
  1. 모든 함수 선언 앞에는 함수가 무엇이고 어떻게 사용하는지, 입력과 출력을 기술해야 한다.

//
// @brief
// A brief description of the function.
// More brief description.
//
// Details follow here.
// Maybe an example or something.
//
// @param value
// Parameter description for parameter named 'value'
//
// @return description
int MyClass::Foo(int value)
{
    return value + 2;
}
  1. 함수 정의부에는 함수가 어떻게 동작하는지(알고리즘)를 기술한다.

2.2.5. 변수 주석

private:
    /// Keeps track of the total number of entries in the table.
    /// Used to ensure we do not go over the limit. -1 means
    /// that we don't yet know how many entries the table has.
    int num_total_entries_;

    /// The total number of tests cases that we run through in this regression test.
    const int kNumTestCases = 6;

    const int kNumTestCases = 6; ///< description for kNumTestCases
  1. 일반적으로 변수 이름만으로도 변수가 무엇을 위해 사용되는지 충분히 알 수 있어야 한다.

  2. 변수의 설명은 변수 설명의 앞에 넣는다. 짧은 경우 오른쪽에 둘 수 있다.

  3. 무엇을 나타내는 변수인지, 무엇을 위해 사용되는지를 기술한다.

2.2.6. 구현 주석

// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++)
{
    x = (x << 8) + (*result)[i];
    (*result)[i] = x >> 1;
    x &= 1;
}

bool success = CalculateSomething(interesting_value,
                                  10, // Default base value.
                                  false, // Not the first time we're calling this.
                                  NULL); // No callback.

const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
                                  kDefaultBaseValue,
                                  kFirstTimeCalling,
                                  null_callback);
  1. 트릭이 있거나 복잡한 코드 블록이 있으면 앞에 주석을 달아야 한다.

  2. 라인 설명을 위해 라인 끝에 주석을 쓸 때는 2공백을 띄우거나 앞, 뒤의 라인 주석과 정렬한다.

  3. NULL, true/false, 1, 2, 3등을 함수의 파라미터로 쓸 때는 주석을 달거나 적절한 변수 이름을 사용한다.

2.2.7. 구둣점, 철자와 문법

주석 내용도 구둣점, 철자, 문법에 맞게끔 완전한 문장으로 작성한다. (라인 끝 주석처럼 짧은 경우는 예외임)

2.2.8. 주석

// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.

소스코드의 개선 사항, 버그, 미완사항등을 임시적으로 표시하기 위해 TODO 주석을 쓸 수 있다.

2.3. Clang Format

들여쓰기나 괄호 위치, 줄바꿈, 빈칸 삽입등은 clang format 도구를 사용하여 자동화 할 수 있다. 다음은 clang-format 프로그램의 입력파일(.clang-format)이다.

---
Language:        Cpp
# BasedOnStyle:  Google
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: true
AlignOperands:   true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
  AfterCaseLabel:  true
  AfterClass:      true
  AfterControlStatement: true
  AfterEnum:       true
  AfterFunction:   true
  AfterNamespace:  false
  AfterObjCDeclaration: false
  AfterStruct:     true
  AfterUnion:      true
  BeforeCatch:     true
  BeforeElse:      true
  IndentBraces:    false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit:     100
CommentPragmas:  '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat:   false
ExperimentalAutoDetectBinPacking: false
ForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
  - Regex:           '"pch.h"'
    Priority:        -1
  - Regex:           '"stdafx.h"'
    Priority:        -1
  - Regex:           '^<.*\.h>'
    Priority:        1
  - Regex:           '^<.*'
    Priority:        2
  - Regex:           '.*'
    Priority:        3
IndentCaseLabels: false
IndentWidth:     4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: BEGIN_MESSAGE_MAP
MacroBlockEnd:   END_MESSAGE_MAP
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right
ReflowComments:  true
SortIncludes:    true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles:  false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard:        Auto
TabWidth:        4
UseTab:          Always
...

3. 기초 코딩 컨벤션

3.1. 네이밍 (Naming)

이름만으로 별도 검색없이 타입, 변수, 함수, 상수, 매크로 여부를 알 수 있도록 일관성있게 적용한다.

3.1.1. 일반 규칙

int num_errors; // Good.
int num_completed_connections; // Good.

// Good
// These show proper names with no abbreviations.
int num_dns_connections; // Most people know what "DNS" stands for.
int price_count_reader; // OK, price count. Makes sense.

OpenFile();
set_num_errors()
int n; // Bad - meaningless.
int nerr; // Bad - ambiguous abbreviation.
int n_comp_conns; // Bad - ambiguous abbreviation.

// Bad!
// Abbreviations can be confusing or ambiguous outside a small group.
int wgc_connections; // Only your group knows what this stands for.
int pc_reader; // Lots of things can be abbreviated "pc".

함수명, 변수명, 파일명은 서술적으로 쓰고 약어를 피한다. 타입, 변수는 명사형이어햐 하고, 함수는 동사여야 한다.

3.1.2. 파일 이름

my_useful_class.cpp
myusefulclass.cpp
myusefulclass_test.cpp

FooBar.h
FooBar.cpp

UrlTable.h
UrlTable.cpp
UrlTableFoo.inc
  1. 파일명은 소문자와 밑줄문자( _ )로 만든다. 또는 대문자로 시작하며 구성하는 단어마다 시작위치에만 대문자를 넣는다.

  2. C++파일은 .cpp로 끝나고 헤더파일은 .h로 끝나야 한다. 예를들어 FooBar 클래스를 정의하는 경우 foo_bar.h, foo_bar.cpp를 만든다.

  3. inline함수는 .h파일에 넣는다. inline함수들의 코드가 아주 크면 .inc로 끝나는 제3의 파일을 만든다.

3.1.3. 타입 이름

// classes and structs
class UrlTable
{

class UrlTableTester
{

struct UrlTableProperties
{

// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;

// enums
enum UrlTableErrors
{
  1. 클래스 이름, 구조체 이름, typedef 이름, enum 이름 모두 동일한 규칙을 적용한다.

  2. 타입 이름은 대문자로 시작하며 구성하는 단어마다 시작위치에만 대문자를 넣는다.

3.1.4. 변수 이름

string table_name; // OK - uses underscore.
string tablename; // OK - all lowercase.

class Test
{
private:
    string table_name_; // OK - underscore at end.
    string tablename_; // OK.
}

struct UrlTableProperties
{
    string name;
    int num_entries;
}
  1. 일반적으로 변수 이름은 소문자와 밑줄문자( _ )만으로 만든다. 대문자를 섞어 쓰지 않는다.

  2. 클래스의 멤버 변수는 밑줄문자( _ )로 끝난다.

  3. 구조체의 멤버(필드)명은 클래스의 멤버 변수와 달리 밑줄문자( _ )로 끝나지 않는다.

  4. 전역 변수는 가급적 정의하지 않는다. 정의할 경우 전역 변수 이름은 g_ 로 시작한다.

3.1.5. 상수 이름

const int kDaysInAWeek = 7;

상수 이름은 k 로 시작하며 대소문자를 혼용한다. 상수는 종류(지역, 전역, 클래스)에 상관없이 일반 변수 이름과 다른 명명 규칙을 적용한다.

3.1.6. 함수 이름

AddTableEntry()
DeleteUrl()
OpenFileOrDie()

class MyClass
{
public:
    ...
    int num_entries() const { return num_entries_; }
    void set_num_entries(int num_entries) { num_entries_ = num_entries; }

private:
    int num_entries_;
};
  1. 일반 함수 이름은 대소문자를 혼용한다.

  2. 접근자(accessor), 수정자(set, mutator)는 변수 이름과 일치시킨다.

3.1.7. 네임스페이스 이름

네임스페이스 이름은 소문자와 밑줄문자( _ )로 구성하고 프로젝트명과 디렉토리구조에 기초한다.

3.1.8. Enumerator 이름

enum UrlTableErrors
{
    kOK = 0,
    kErrorOutOfMemory,
    kErrorMalformedInput,
};
  1. Enumerator 이름은 상수이름과 동일한 형식으로 만든다.

  2. Enumeration (enum) 이름은 타입 이름에서 정했듯이 대소문자를 혼용한다.

3.1.9. 매크로 이름

매크로는 가급적 정의하지 않는다. 매크로 이름은 대문자와 밑줄문자( _ )로 구성한다.

3.1.10. 예외 사항

bigopen() // function name, follows form of open()
sparse_hash_map // follows STL naming conventions
LONGLONG_MAX // a constant, as in INT_MAX

C 또는 C++에 이미 있는 것과 연관지을 수 있을때, 이미 있는 것의 명명 규칙을 따를 수 있다.

3.2. 헤더파일 (Header Files)

main() 함수만 있는 작은 cpp파일처럼 특별한 경우를 제외하면, 일반적으로 cpp파일마다 연관된 .h파일이 있어야 한다. 헤더파일은 자체 완비(self-contained) 되어 있어야 한다.

3.2.1. define 가드

// Copyright 문

#ifndef FILE_IO_H_
#define FILE_IO_H_

// src/common_code/file_system/file_io.h 파일인 경우

#endif // FILE_IO_H_

// 또는

#ifndef COMMON_CODE_FILE_SYSTEM_FILE_IO_H_
#define COMMON_CODE_FILE_SYSTEM_FILE_IO_H_

// src/common_code/file_system/file_io.h 파일인 경우

#endif // COMMON_CODE_FILE_SYSTEM_FILE_IO_H_
  1. 모든 헤더파일은 중복 include를 방지하기 위해 #define 가드를 넣는다.

  2. 정의기호는 파일명(대문자)_ 형식으로 한다. 동일한 파일 이름이 생길 가능성이 높을 때는 디렉토리명_파일명(대문자)_ 형식으로 한다.

3.2.2. 헤더파일 종속성

포워드 선언을 써서 헤더파일에서 다른 헤더파일을 include하는 것을 줄인다.

3.2.3. 인라인 함수

인라인 함수는 15라인이내로 짧을 때만 정의한다. 인라인 함수, 함수 템플릿의 구현은 .h 파일에 선언하고 구현한다.

3.2.4. .inc 파일

헤더파일이 아닌 단순 inclusion 목적의 파일은 .inc 확장자를 사용한다.

3.2.5. 함수 파라미터 순서

함수 파라미터 순서는 입력, 출력 순으로 한다. 입출력 파라미터도 입력전용 파라미터 보다 뒤에 오도록 한다.

3.2.6. 인클루드 이름과 순서

#include "foo/public/fooserver.h" // Preferred location.

#include <sys/types.h>
#include <unistd.h>

#include <hash_map>
#include <vector>

#include <boost/interprocess/sync/named_mutex.hpp>
#include <boost/shared_ptr.hpp>

#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"
  1. 헤더파일 include순서는 C library, C++ library, 3rd party library, Project library 순으로 하고 라이브러리별(C, C++, 3rd party, Project)로 알파벳 순서로 include한다.

  2. 프로젝트 헤더파일은 소스 디렉토리 기준으로 (.. : 부모디렉토리)를 사용하지 않는 상대 경로로 표시한다.

  3. 헤더파일을 구현하는 cpp에서 include하는 경우 제일 먼저 include한다.

3.3. 기타 규칙 (Misc Rules)

3.3.1. 매크로 (Macros)

  1. 매크로는 가급적 정의하지 않는다. 매크로 대신 inline 함수, enums, const 변수를 사용한다.

  2. .h 파일안에 매크로를 정의하지 않는다.

  3. #undef 로 이미 존재하는 매크로를 자신의 것으로 바꾸지 않는다. 대신에 다른 유일한 이름을 고른다.

  4. 함수명, 클래스명, 변수명을 생성하기 위해 ##를 사용하지 않는다.

3.3.2. 캐스팅 (Casting)

캐스팅을 쓸 때는 y = (int) x; 형식 대신 static_cast<int>(x) 형식을 쓴다.

4. 고급 코딩 컨벤션

4.1. 클래스 (Classes)

4.1.1. 멤버 선언 순서

class MyClass : public OtherClass
{
public: // Note the no indent!
    MyClass(); // Regular 1 tab 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_;
    DISALLOW_COPY_AND_ASSIGN(MyClass);
};
  1. base 클래스명은 서브클래스명과 같은 라인에 적는다.

  2. public:, protected:, private: 키워드는 들여쓰기 하지 않는다.

  3. public, protected, private 순서로 구역을 나눈다. 빈 줄로 구역을 구분한다.

  4. 각 구역마다 method, data member 순으로 선언한다. 자세한 순서는 다음과 같다.

    • Typedef, Enums, Constants (static const)

    • 생성자, 소멸자

    • method, data member (except static const)

  5. friend 선언과 DISALLOW_COPY_AND_ASSIGN 매크로는 private 구역에 있어야 한다.

4.1.2. 생성자에서의 작업

생성자에서는 멤버 변수의 초기값만 세팅하고 다른 복잡한 초기화 작업은 Initialize() 메소드에서 한다. (생성자에서 발생한 에러는 체크하기가 어려우므로 에러가 발생할 가능성이 있는 모든 작업은 Initialize() 메소드에서 한다.)

4.1.3. 디폴트 생성자

멤버변수가 있는 클래스를 정의하면서 어떤 생성자도 정의하지 않았다면 디폴트 생성자를 정의해야 한다. 상속 클래스에서 멤버변수 추가가 없는 경우에는 필요없다.

4.1.4. Explicit 생성자

인자개수가 1개인 생성자에는 explicit 키워드를 사용하여 암묵적으로 conversion용도로 사용되는 것을 방지한다.

4.1.5. 복사 생성자

  1. 복사 생성자와 대입 연산자는 필요할 때만 정의하여 사용하고, 그렇지 않으면 DISALLOW_COPY_AND_ASSIGN를 써서 사용할 수 없게 만든다. (묵시적인 객체복사는 버그가 발생하기 쉽고 성능과 가독성에 안좋은 영향을 주기 때문이다.)

  2. 복사 생성자가 필요한 경우에도 가능하면 CopyFrom() 메소드를 정의하여 복사 생성자를 대신한다.

4.1.6. 구조체(structs)와 클래스(classes)

  1. 구조체는 자료를 담는 수동적인 객체를 위해서만 사용하고, 그 밖의 것은 클래스를 사용한다.

  2. 구조체에는 상수도 정의할 수 있으나 필드(멤버)를 읽고 쓰는 기능외에는 없다.

  3. 필드를 읽고 쓸 때는 메소드를 사용하지 않고 직접 필드에 접근한다.

  4. 구조체의 메소드는 필드를 setup 할 때만 사용한다. 예를들면, 생성자, 소멸자, Initialize(), Reset(), Validate() 등이다.

  5. 기능성이 더 필요한 경우에는 클래스를 사용한다.

  6. STL과의 일관성을 위해 functors 나 traits에 대해 클래스 대신 구조체를 사용할 수 있다.

  7. ‘변수 이름’에서 정했듯이 구조체와 클래스 멤버 변수의 명명규칙은 다르다.

4.1.7. 클래스 상속

  1. 클래스 상속은 모두 public으로 한다.

  2. 가능하면 구현 상속보다는 Composition을 사용한다.

  3. IS-A 관계인 경우에만 클래스 상속을 사용한다.

4.1.8. 다중 상속

다중 상속을 할 때 하나의 클래스로 부터만 구현 상속을 하고 다른 클래스는 모두 인터페이스 클래스여야 한다.

4.1.9. 인터페이스

클래스 이름이 Interface로 끝나는 경우 아래 조건을 모두 만족해야 한다.

  1. public pure virtual 메소드와 static 메소드만 있다.

  2. static 아닌 데이터 멤버가 없다.

  3. 생성자가 없어도 된다. 생성자가 있다면 인자 없이 protected 영역에 정의되어 있어야 한다.

  4. 서브클래스인 경우 부모클래스의 이름이 Interface로 끝난다.

4.1.10. 연산자 오버로딩

클래스에 대한 +, /, =, == 같은 연산자 오버로딩은 사용하지 않는다.

CopyFrom(), Equals() 같은 일반 메소드를 =, == 대신 정의하여 사용한다.

4.1.11. 접근 제어

class SampleClass
{
public:
    static const int kMaxRetry = 3;
    inline int foo() { return foo_; } // accessor 함수
    inline void set_foo(int foo) { foo_ = foo; } // mutator 함수

private:
    int foo_; // Sample
};
  1. 데이터 멤버는 private으로 선언하고 필요한 경우 accessor 함수를 제공한다.

  2. 변수명, accessor 함수, mutator함수는 foo_, foo(), set_foo() 형식를 가진다.

  3. static const 데이터 멤버는 private이 아닐 수 있다.

4.1.12. 메서드 함수 길이

주석을 제외한 메서드 함수 몸체의 길이가 100라인을 넘어가면 가급적 함수를 분할한다. 클래스의 메서드가 아닌 일반 함수에 대해서도 같은 규칙을 적용한다.

4.2. 범위 (Scoping)

4.2.1. 네임스페이스

// This is in a .cpp file.
namespace {

// The content of a namespace is not indented
enum { kUnused, kEOF, kError }; // Commonly used tokens.
bool AtEof() { return pos_ == kEOF; } // Uses our namespace's EOF.

} // namespace
  1. 이름 충돌을 피하기위해 이름없는 네임스페이스를 cpp파일에 사용할 수 있다.

  2. 네임스페이스에 이름을 붙일 경우 프로젝트명과 파일경로에 기초하여 정한다.

#include "a.h"

DEFINE_bool(someflag, false, "dummy flag");

class C; // Forward declaration of class C in the global namespace.
namespace a { class A; } // Forward declaration of a::A.

namespace b {

// Definition of functions is within scope of the namespace.
void MyClass::Foo()
{
    ...
}

} // namespace b
  1. 네임스페이스 영역은 들여쓰기를 하지 않는다.

  2. 네임스페이스는 include문, 타 네임스페이스의 클래스 포워드선언 이후 전체 소스를 감싼다.

// Forbidden -- This pollutes the namespace.
using namespace foo;
  1. 헤더 파일내에 using 지시어(using-directive)는 사용하지 않는다.

// OK in .cc files.
// Must be in a function, method or class in .h files.
using ::foo::bar;

// Shorten access to some commonly used names in .cc files.
namespace fbz = ::foo::bar::baz;

// Shorten access to some commonly used names (in a .h file).
namespace librarian {
// The following alias is available to all files including
// this header (in namespace librarian):
// alias names should therefore be chosen consistently
// within a project.
namespace pd_s = ::pipeline_diagnostics::sidetable;

inline void my_inline_function()
{
    // namespace alias local to a function (or method).
    namespace fbz = ::foo::bar::baz;
    ...
}
} // namespace librarian
  1. using 선언문(using-declaration)과 네임스페이스 alias를 사용한다.

4.2.2. 비멤버 함수, 정적 멤버함수, 전역함수

  1. 전역함수를 정의하는 대신 네임스페이스안에 비멤버함수를 정의하거나 정적 멤버함수를 정의한다. (클래스의 멤버함수가 아닌 함수를 모두 비멤버함수라고 한다.)

  2. 비멤버함수는 외부변수에 의존하지 않고 한 네임스페이스안에 있어야 한다.

  3. static 데이터를 공유하지 않는 정적 멤버함수만 모아놓은 클래스를 만드는 대신 네임스페이스를 사용한다.

  4. 동일한 .cpp파일 내에서만 필요한 비멤버함수를 정의 할 때는 이름없는 네임스페이스 또는 static 키워드를 써서 범위를 한정시킨다.

4.2.3. 지역 변수

int j = g(); // Good
int i;
i = f(); // Bad

함수내의 변수선언은 가장 좁은 범위에서 변수의 최초 사용과 가까운 곳에 하고 선언하는 곳에서 초기화를 한다.

4.2.4. 정적변수와 전역변수

클래스 타입의 정적변수와 전역변수는 사용하지 않는다. (생성자와 소멸자의 정해지지 않은 순서로 인해 찾기 힘든 버그를 만들기 때문)

5. 기타

5.1. 적용 범위

5.1.1. 비준수 코드

기존에 작성된 파일의 일부분을 수정할 때, 파일 단위의 일관성을 위해 기존의 스타일을 따를 수 있다.

5.1.2. 윈도우 코드

윈도우 프로그래밍은 MS에 기인한 고유의 코딩 컨벤션이 있으나 본 가이드를 적용한다.

  1. 본 지침서와 다른 헝가리언 표기법은 사용하지 않는다. (예: 정수 변수 iNum)

  2. MSVC++을 사용할 때 warning level을 3이상으로 설정한다.

  3. 예외적으로, COM, ATL에서는 다중상속을 사용할 수 있다.

  4. 가능하다면 ATL, STL에서 exception을 쓰지 않도록 설정한다.

  5. 예외적으로, 매크로 정의 파일인 resource.h는 본 가이드를 적용하지 않는다.