C++ 스타일 가이드
1. 소개
2. 명명 규칙
- 아래에서 허용되는 접두사 외에 다른 접두사를 사용하는 것은 금지되어 있습니다.
밑줄 사용(_)은 금지되어 있지 않지만 가능하면 피하십시오. 이것은 이름의 첫 번째 또는 마지막 문자로 사용할 수 없습니다.
표준 C 함수 이름(예: floor, abs)은 이름으로 사용해서는 안 됩니다.
-
#define 이름
대문자로 작성해야 합니다; 2개 이상의 단어로 구성된 이름은 단어 사이에 밑줄(_)로 분리해야 합니다.
예: NULL, PLATFORM, DEBUG_LEVEL_2, REFERENCE_CHECK
NULL은 0 포인터를 표시하기 위해 사용되어야 합니다.
-
Type 이름 (struct, enum, typedef, class, namespace, template parameter)
첫 글자는 대문자입니다; 모든 새로운 단어 역시 대문자로 시작해야 합니다. 인터페이스 이름의 경우 무조건 "I" 접두사를 넣어야 합니다.
예: Vector2D, Array, SymbolSet, Char, IComparable
-
Variable 이름
소문자로 시작합니다; 모든 새로운 단어 역시 대문자로 시작해야 합니다. 모든 멤버 변수들은 "m" 접두사(예: mLength)를, 글로벌 변수들은 "g" 접두사(예: gWindowCount)를 가질 수 있습니다. 글로벌 변수들은 :: 글로벌 scope로 표시할 수 있습니다.
예: size, isDeletable, newFont
-
Constants (const, enum)
첫 글자는 대문자입니다; 모든 새로운 단어 역시 대문자로 시작해야 합니다. 공통 scope에서 정의된 enums의 이름은 달라야 합니다. 그래서 여기서는 접두사를 사용해도 됩니다.
예: Underscore, DefaultSize
_ -
Function 이름
첫 글자는 대문자입니다; 모든 새로운 단어 역시 대문자로 시작해야 합니다.
E.g.: Print, SetPort
-
Method 이름
첫 글자는 대문자입니다; 모든 새로운 단어 역시 대문자로 시작해야 합니다.
되도록이면 이름에 동사를 사용하거나, 동사를 포함하는 구조를 사용하십시오. (예. verb + object) 예: Move, FindLast, AppendName, DeleteIcon.
동사 Set, Get, Has는 속성 정보를 세트하거나 가져오는 method 이름으로 사용해야 합니다. 예. SetSize, GetSize, IsEmpty, HasIcon
이름을 지을 때에는 'To' 접두사로 시작해야 합니다. 그리고 동사는 없어야 합니다. 예: ToString, ToDouble
-
Parameter 이름
입력, 출력 또는 입/출력 파라미터들의 경우 in/out/io 접두사를 사용해도 됩니다. (예: inString, outText, ioLength)약어는 대문자 규칙에서 예외입니다. 예. ISO_Standard, 여기서 ISO는 모두 대문자로 작성합니다.
3. 표현식
- 다음 2항 연산자 앞뒤로 공백을 사용해야 합니다:
- 산술 연산자: *, /, %, +, -, <<, >>
- 논리 연산자: &&, ||, ==, !=, <, <=, >, >=
- 비트조작 연산자: &, ^, |
- 할당 연산자: =, *=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^=
a = b + c % d;
x += y;
double r = 25 * b / 2 + 4;
- 다음 2항 연산자 앞뒤로는 공백을 허용하지 않습니다:
- Scope 선택: className::member
- Member 선택: object.member, pointer->member, object.*member, pointer->*member
- 단항 연산자 앞뒤로는 공백을 허용하지 않습니다:
- 전, 후 / 증, 감: ++i, --i, i++, i--
- subscripting: array[25]
- 보수(complement): ~x
- 논리적 not: !x
- 단항 minus, 단항 plus: -x, +x
- 변수의 주소값: &var
- dereference: *p
- global scope: ::globVar
- 다음 (특수) 단항 연산자들 앞뒤에는 공백을 사용해야 합니다:
- 함수 호출: SetItem (25, item)
- casting: (char*) p
- 실행-시간 type 식별: typeid (Vector)
- object 또는 type의 크기: sizeof (*v), sizeof (Vector)
- 할당, 해제: new type, delete p, delete [] p
- 예외 throw하기: throw Exception
- 함수 호출은 파라미터 리스트의 여는 괄호 앞에 공백을 넣어야 합니다. 그리고 파라미터를 분리하는 모든 콤마 뒤에도 공백을 넣어야 합니다:
sin (PI / 2);
double average = Average (i, 25 * j, 45);
Stop (); - 상수와 비교할 때 (예. i == 5) 상수는 반드시 2번째 피연산자이어야 합니다.
- 조건 표현식에서 조건은 반드시 괄호로 감싸야 합니다. '?'와 ':' 연산자 앞뒤에는 공백이 있어야 합니다:
Int32 val = (a < 5) ? 0 : a * 2;가능하면 이런 조건 구문을 끼워넣는 것을 피하십시오.
- 콤마 (시퀀싱) 연산자 뒤에 공백을 넣으세요:
for (int i = 0; i < 10; i++, j++)부디 콤마 연산자를 남용하지 마십시오.
- C++에서의 casting:
p = static_cast<Vector*> (pVec);C++ 코드에서 C-스타일 casting은 허용하지 않습니다.Vector& v = dynamic_cast<Vector&> (*pVec);
- C-스타일 casting (C 코드 또는 C++로 컴파일된 C 코드에서만 사용할 수 있습니다):
pc = (char*) p;
pc = (int*) (q->c + 3); - bool 변수에 값을 할당할 때, 논리 표현식은 반드시 괄호로 감싸야 합니다:
bool b = (i > j);
- 자동 bool 변환을 사용하지 마십시오; 코드 안에 조건을 명시적으로 작성하십시오:
unsigned int i = f ();
if (i != 0) { // if (i) 대신
...
}char* p = g ();
또한 논리적 부정의 bool 결과를 사용하지 마십시오. 예. 부디 !i 대신 i == 0를 사용하십시오. 그리고 !p 대신 p == NULL 조건을 사용하십시오.
if (p != NULL) { // if (p) 대신
...
}
(물론, bool 변수 및 표현식은 부정할 수 있습니다. 예. !b)
- bool 변수 및 표현식을 true 또는 false와 비교하지 마십시오; bool의 값 또는 그것의 부정 값을 사용하십시오:
bool b = true;
if (b) { // if (b == true) 대신
...
}if (!b) { // if (b == false) 대신
...
} - 여러 부분으로 구성된 논리적 표현식은 다중 라인으로 배치해야 합니다. 또한 서브 표현식이나 논리적 연산자를 정렬해야 합니다. 그리고 body 앞에는 빈 라인을 남겨 두십시오.:
if ((a > 2 && b < 1) ||복잡한 표현식(예. 여기서 &&와 || 둘 다 있음)은 괄호로 감싸야 합니다. 그래서 우선순위가 항상 명확해야 합니다.
(c > 10 && d == 0) ||
(e == -5) ||
(f < 5 && f > 1))
{
// body
}
- 동일한 타입의 밀접하게 관련된 변수들에 한해서만 연쇄 할당을 사용할 수 있습니다. (예: i = j = 0)
- 거의 사용하지 않는 연산자 조합은 괄호로 감쌉니다.
4. 제어 흐름 구문
-
if-else
- if (condition)
statement;
- if (condition) {
// body
}
- if (condition)
statement1;
else
statement2;
- if (condition) {
// body
} else {
// body
}
- if (condition1) {
// body
} else if (condition2) {
// body
} else if (condition3) {
// body
} else {
// body
}
if (condition1) {만약 조건적 if 구문 안에 complex branch가 있다면 모든 것이 complex 이어야 합니다.
if (condition2)
statement1;
else
statement2;
}
condition은 어떤 할당도 포함해서는 안 됩니다. - if (condition)
-
switch
switch (key) {default branch를 필수적으로 갖고 있어야 합니다 (오류 처리!) default 는 항상 마지막에 있어야 합니다.
case Value1:
// body
break;case Value2:
// body
// no breakcase Value3:
case Value4:
// body
break;case Value5:
}
// local 변수를 갖고 있는 body
}
break;default:
// body
break;
}
만약 비어 있지 않은 case branch의 body를 실행한 후에 다음 case branch에서 continue하게 되면 "no break" 코멘트를 추가하는 것은 필수입니다.
무조건적인 return 및 throw 구문 뒤에는 break를 추가하면 안 됩니다.짧은-body 유사한 case branch의 경우(예. conversion), 당신은 좀 더 가독성 있는 콤팩트한 switch 형태를 사용할 수 있습니다:
switch (code) {
case 'I': return 1;
case 'R': return 2;
case 'B': return 3;
case '\n': return 4;
default: return 0;
} -
for, while, do-while
- for (Int32 i = 0; i < 100; i++)
statement;
- for (Int32 i = 0; i < 100; i++) {
// body
}
- for (Int32 i = 0; i < 100; i++)
; // 빈 body
만약 loop 변수가 loop 안에서만 필요하다면, 위의 예제들처럼 for loop의 header에서 정의할 수 있습니다
만약 for loop의 head로부터 어떤 구문이라도 빠뜨린다면, '(', ';', or ')' 문자 사이에 공백을 두어야 합니다. 예: for ( ; ; )
- while (condition)
statement;
- while (condition) {
// body
}
condition 안에 할당을 사용하지 마십시오.
- do
statement;
while (condition);
- do {
// body
} while (condition);
condition 안에 할당을 사용하지 마십시오.
- for (Int32 i = 0; i < 100; i++)
-
try-catch-throw
try {만약 어떤 문제도 발생하지 않는다면 catch 가 허용하는 type 및 parameter 이름을 작성하는 방법은 다음을 권장합니다:
// body
}
catch (Type1 exception) {
// body
}
catch (Type2) {
// body
}
catch (...) {
// body
throw; // rethrow
}
catch (const Type& exception) 또는
catch (const Type& e) 또는
catch (const Type&)(만약 Type이 basic type이 아닌 경우)
5. 변수 선언
- 가장 좁은 scope 안에 모든 변수들을 선언하십시오. 예. 만약 if branch에서만 필요로 하는 유틸리티 변수가 있다면 거기서 선언하십시오.
함수들의 static 변수들은 함수의 시작 부분에서 선언해야 합니다. 같은 함수 안에서는 동일한 이름의 변수를 사용하지 마십시오.
주의: (for, while, do) loop의 scope 안에서 선언된 변수들의 생성자는 매 iteration마다 호출됩니다. 때로는 이것은 당신이 필요로 하는 것이지만 성능 문제에 직면할 수도 있습니다.
- Class-type 변수(객체)는 적절하게 초기화할 수 있는 곳에서 선언되어야 합니다. (기본 생성자 + 나중에 할당하는 것은 적절한 생성자를 사용하는 적절한 초기화만큼 효율적이지 않음)
기본 type은 가장 좁은 scope의 시작 부분에서 선언되어야 합니다.
만약 (local) 변수를 선언할 때 제대로 초기화할 수 없는 경우 더미 값(예. 0)을 주지 마십시오. 그래야 당신이 branch 중 하나에서 변수를 초기화하는 것을 잊은 경우 컴파일러가 알려줄 수 있습니다.
만약 컴파일러가 잘못된 warning을 주면 불필요한 초기화 장소에서 comment에 이것을 표시하십시오.
- 모든 변수들은 분리된 라인으로 선언(및 초기화)되어야 합니다. 이것은 global (static) 및 class data member 변수, pointer, reference에 대해 필수입니다. 이 규칙의 예외는 매우 밀접하게 관련된 local 변수들인 경우입니다. 예:
double x, y, z;만약 당신이 위의 규칙들을 따른다면(그리고 20-페이지 함수들을 작성하지 않는다면), 한 장소 안에 너무 많은 변수 선언들을 두지 마십시오.
- scope의 열린 bracket('{')가 있는 라인에서 변수들을 정의하지 마십시오.
- local 및 global (static) 변수 선언 및 초기화하기:
Int32 index = 3;type과 name 사이에, 그리고 = 앞뒤에 공백을 두십시오. 이렇게 하면 하나의 선언이 구문들과 잘 들어맞습니다. 둘 이상의 선언 라인을 배치하는 경우, 가능하다면 name과 equal 기호를 정렬하십시오. 논리적으로 보이는 경우 선언의 앞, 중간 또는 뒤에 빈 선을 추가할 수 있습니다. 만약 논리적으로 보일 수 있다면 선언들 앞뒤나 중간에 빈 라인을 추가할 수 있습니다.
포인터의 '*'와 레퍼런스의 '&'는 type 이름 옆에 붙어 있어야 합니다:
char* ptr = NULL;1개의 라인에 다수의 포인터나 레퍼런스를 절대 선언하지 마십시오.
Int32& index = oldIndex;
배열 변수 이름과 '[' 문자 사이에 공백을 두지 마십시오:
char array[20];
char* numbers[] = { "One", "Two", NULL };
- class data member의 선언은 local 변수를 선언하는 것과 비슷합니다. 그러나 class data member의 경우 어딘가에서 (명확하게) 초기화가 이루어지며 그 수가 많은 이유로 더 엄격하게 할당이 이루어집니다.
data member 선언에 대해서는 Data member 선언 챕터를,
구현 이슈에 대해서는 embedded classes 구현 방법 챕터를 보십시오.
- global 변수와 상수는 항상> 사용법을 설명하는 comment가 함께 있어야 하며, 이름을 선택할 때 큰 주의를 기울여야 합니다. (k, myDrw 또는 newTKDef가 되어서는 안 됨)
더 긴 이름의 변수만 자신의 역할을 설명할 수 있는 경우 이 변수를 사용하십시오.
(많은) global 변수를 사용하지 마십시오. 모든 것을 namespace에 넣습니다.
- 만약 "variable"가 상수이면 const로 선언하십시오:
const Vector& v = translation;
- (글자 그대로의) "적나라한" 값을 코드에 사용하지 마십시오; 그것들에게 상수를 할당하십시오.
- 명명 규칙: => 변수 이름 및 상수 챕터를 보십시오.
6. 함수
-
선언
- return 값의 type과 함수 이름 사이에 1 (또는 첫 번째 공백 거리가 1개만 제공된 경우 2개) 탭을 배치하십시오.
또한 파라미터 리스트의 열린 괄호 앞에 공백을 두고 각 파라미터 구분 콤마 뒤에 공백을 남겨 두십시오. 예:
void DrawPixel (Int32 x, Int32 y);또한 type과 파라미터의 이름 사이에 공백을 두십시오.
short GetVersion (void);
만약 당신이 2개 이상의 변형된 키워드를 갖고 있다면, 함수 이름은 새로운 라인으로 이동할 수 있습니다:
static int cdecl
SetPointer (char* p);
- 만약 default 파라미터를 갖고 있다면, '=' 앞뒤에 공백을 두십시오:
void PrintNumber (Int32 n, short radix = 10); - 만약 여러 파라미터들과 함수 선언을 한 라인에 넣을 수 없다면, 모든 파라미터는 각자 새로운 라인에 두고 name과 type은 정렬해야 합니다. 예:
String ConcatenateName (const String* part1,
const String* part2,
const String* part3,
const String* part4,
bool appendNewLine = false);
- 가능할 때마다 const를 사용하십시오.
- 만약 함수가 어떤 파라미터도 갖고 있지 않은 경우 항상 void를 쓰십시오.
예외: 기본 생성자, 소멸자, 정의에 의한 파라미터들을 갖고 있지 않은 연산자들.
- 레퍼런스로 값을 뒤로 전달하거나 변수를 초기화하는 데 사용하지 마십시오. 대신 포인터를 사용하십시오.
반대로 레퍼런스를 값 또는 객체 수정을 위해 사용할 수 있습니다. (입/출력 파라미터)
- bool 파라미터는 가장 단순한 case 안에만 전달하십시오. 대신 enum을 사용하십시오.
- 오버로딩하는 연산자는 natural conversion에서만 사용하십시오. 연산자는 operator 키워드 바로 뒤에 따라와야 합니다.
- 명명 규칙: => Function 이름을 보십시오.
- return 값의 type과 함수 이름 사이에 1 (또는 첫 번째 공백 거리가 1개만 제공된 경우 2개) 탭을 배치하십시오.
또한 파라미터 리스트의 열린 괄호 앞에 공백을 두고 각 파라미터 구분 콤마 뒤에 공백을 남겨 두십시오. 예:
-
구현
- 스타일:
void PrintNumber (Int32 n, short radix)
{
// body
}
- 함수 헤더는 함수 선언에 대해서 위에서 설명한 규칙을 따라야 합니다.
- 함수의 body를 작성할 때 오른쪽으로 1개의 탭만큼 들여쓰기 해야 합니다.
- 함수의 닫는 '}' 전에 2개의 빈 라인을 남겨두어야 합니다.
- 라인당 1개의 구문을 작성해야 합니다.
- 함수는 80 라인을 초과하지 마십시오.
- 너무 콤팩트한 구조, 매우 복잡한 표현식에 마법의 C 트릭을 쓰지 않도록 하십시오.
- 조건적 표현식은 최대 3 레벨 깊이까지만 유지하십시오; 만약 더 깊이 들어가게 되면 분리된 (inline) 함수로 코드를 분리하십시오.
- 많은 (2개 이상의) return 구문을 사용하지 않도록 하십시오.
또한 함수의 시작 부분에 조건적인 return을 제외하면, 함수 body의 중간에서 return 구문을 두지 않도록 하십시오.
- 함수 body의 시작 부분에 들어오는 파라미터(전제조건)들을 확인하기 위해 Assert를 사용하십시오.
- local (또는 static) 변수의 선언은 변수 선언 챕터에서 논의했습니다.
- 스타일:
7. Type 정의
-
struct
struct Vector {struct에 대하여 default public 키워드를 작성하지 마십시오. 그리고 protected 또는 private 섹션을 사용하지 마십시오. 만약 당신이 그러한 섹션들이 필요하다면 대신 class를 작성하십시오. 마찬가지로 static data member와 상속을 사용하지 마십시오.
double x; // Vector의 x 좌표
double y; // Vector의 y 좌표
double z; // Vector의 z 좌표bool isNormalized; // 만약 true이면, Vector는 Normalized입니다
};
Data member 선언에 대한 다른 규칙들은 class에 대한 Data member 선언 챕터에서 찾을 수 있는 것과 같습니다.만약 생성자가 필요하다면, data member와 생성자 사이에 빈 라인을 두십시오. 예:
struct Point {Structure는 C 언어에서 익숙한 의미로만 사용할 수 있습니다. Structure는 data member 옆에 생성자와 생성자 같은 (Set) method들을 가질 수도 있습니다.
Int32 x; // Point의 x 좌표
Int32 y; // Point의 y 좌표Point ();
Point (Int32 xc, Int32 yc);
};
Method 선언에 대한 다른 규칙들은 class에 대한 Method 선언 챕터에서 찾을 수 있는 것과 같습니다.
Embedding structures into classes에 대한 정보는 Embedded classes 및 structures 챕터를 보십시오.
-
union
-
enum
enum Status { OK, Error }; // 많은 method의 return 값값 할당의 경우 '=' 기호와 값들을 모두 정렬하십시오:enum DataType { // interpreter의 가능한 데이터 type
Int,
Real,
Bool,
String,
Object,Void
};
enum Token // tokenizer 상수부디 역할을 설명하는 comment를 enum에 추가하십시오.
{
Integer = 25,
Real = 48,
Character = 701
};
각 플랫폼에서 enum의 물리적 크기가 다를 수 있음을 유념하십시오. 그러므로, 가량 enum을 포함하는 structure의 길이가 달라질 수 있습니다. 만약 이것을 바이너리 형태로 작성하면 문제가 발생할 수 있습니다. 이것을 피하려면 컴파일하기 전에 enum의 크기를 int의 크기로 바꾸십시오.
만약 enum이 파일에 기록된다면 enumerator에게 값을 부여하십시오.
-
typedef
typedef Int32 Offset;typedef 포인터를 피하십시오.typedef unsigned Int32 Index;
typedef VectorFS<double, 3> Vector3D;
8. Class 정의
-
Base structure
-
Visibility - 개발자의 관점(private, protected, public), 그리고 사용자의 관점(public, protected, private)이 허용됩니다. 각 섹션에는 하나의 섹션만 있을 수 있습니다. 예:
class Vector {만약 class의 헤더가 여러 라인에 걸쳐져 있으면 class body의 열리는 bracket '{'은 새로운 라인으로 가야 합니다. (상속을 보십시오) 드문 경우, public (또는 protected) 파트가 선언들을 포함합니다. (주로 사용자 type, 예. enum) 이 선언들은 private (또는 protected) 섹션에서 사용됩니다. 이 선언들은 class의 시작 부분에 보여야 합니다:
private:
// private partprotected:
// protected partpublic:
// public part
};
class Vector {
public: // predefinitions
typedef double Coord;
// private (protected) part에서도 사용하는 다른 public 정의private:
// private partprotected:
// protected partpublic:
// public part
}; -
Template classes
template <class Type, int BufferSize = 16>
class Array {
// body
};Template 인수는 함수의 인수와 비슷하게 취급되어야 합니다.
-
상속
class Derived: public Base1,상속 시에는 항상 default private 키워드를 작성해야 합니다.
public Base2,
protected Base3,
private Base4
{
// body
};
가상 상속을 위해 public virtual Base ordering을 사용하십시오.
단일 상속의 경우 열리는 bracket '{'은 헤더 라인의 끝으로 가야 합니다. (class ...) - 명명 규칙
-
Visibility - 개발자의 관점(private, protected, public), 그리고 사용자의 관점(public, protected, private)이 허용됩니다. 각 섹션에는 하나의 섹션만 있을 수 있습니다. 예:
-
Body
-
private 섹션 - 일반적인 레이아웃, 권장사항, 규칙
class String {더 나은 가독성을 위해 default private 키워드가 항상 class의 시작 부분에 나타나야 합니다.
private:
// definitionstypedef short Symbol;
enum InternalStatus { OK, Error };
// static (class) data membersstatic const Int32 MaxSize; // 짧은 설명
static char defChar; // 짧은 설명// normal (instance) data members
char* str; // 짧은 설명
Int32 size; // 짧은 설명// static (class) functions
static unsigned char* ToPascalString (const char* cStr);
// normal (instance) member functions (methods)
void Clear (void);
Int32 SkipSign (Int32 from) const;
inline Int32 SkipDigits (Int32 from) const;
virtual void Print (void) const;
protected:
// protected partpublic:
// public part
};
Disabled methods (예. constructors)에 // disabled comment를 붙이고 일반 method 리스트의 시작 부분에 두십시오. 예:
String (const String& source); // disabled만약 friend classes 또는 functions를 사용한다면, 모든 private data와 methods에 대해 알 필요가 있을 경우 private 섹션의 시작 부분에 나와야 합니다.:
String& operator= (const String& source); // disabled
private:
friend class Menu; // must be friend because ...
friend 클래스가 약간의 private methods 또는 data만 필요하다면, 그것들 바로 옆에 friend 선언을 두십시오. 그렇게 하면 사용자의 friend class가 우리의 class로부터 사용하는 내용을 볼 수 있을 것입니다. (이것은 우리 class에 접근할 수 있는 그러한 class들의 즉각적인 가시성을 파괴합니다)
당신은 friend 선언 뒤에 짧은 comment로 반드시 설명해야 합니다. (동일한 라인 상에)
(이 규칙의 예외는 그것들의 public friend 연산자 함수들이 normal method가 아니어서 컴파일러가 연산자의 대칭성을 보장합니다. 예. Vector class의 operator+ (const Vector& v1, const Vector& v2)friend 함수). -
protected 섹션 - 일반적인 레이아웃, 권장사항, 규칙
protected 섹션에 대한 특별한 권장사항과 규칙은 없습니다. 예외적으로 다음 권장사항이 있습니다:
protected data members를 가능한 적게 사용하십시오; 대신 protected (하다못해 inline) methods를 사용하십시오.
-
public 섹션 - 일반적인 레이아웃, 권장사항, 규칙
class String {
private:
// private partprotected:
// protected partpublic:
// definitionstypedef char* CStr;
enum SymbolType { ASCII, WideChar };
// 선택적인 예외 classes의 선언 (types)
// static const (class) data membersstatic const char NewLine = '\n'; // 짧은 설명
static const char MaxLength = 256; // 짧은 설명// static (class) functions
static char* GetClassName (void);
// Constructors
String ();
String (const char* cStr);
String (const String& source);
~String ();
String& operator= (const char* cStr);
String& operator= (const String& source);// normal (instance) member functions (methods)
void Delete (Int32 idx);
void Delete (Int32 from, Int32 range);
void DeleteLast (void);inline void Clear (void);
void Replace (Int32 from, Int32 range, char ch);
inline char& operator[] (Int32 idx);
inline char operator[] (Int32 idx) const;virtual Int32 Read (IChannel& ic);
};Class에서 public non-const data members를 사용하지 마십시오.
-
Data member 선언
- Data members(라인당 1개)는 서로 아래에 나타나야 하며, 각 member 뒤에 짧은 설명을 붙여야 합니다.
- 만약 data member가 많으면 정렬하십시오. type과 name 사이에는 최소한 2개의 공백을 두십시오. 예:
char* str; // 할당된 문자열을 가리킴 (만약 없으면 NULL)
UInt32 size; // 문자열의 크기
- 만약 data member가 많으면, 논리적 그룹 간에 빈 라인을 두십시오.
- 만약 member가 많은 수정된 키워드(static, const, mutable)를 갖고 있다면, 이것이 올바른 순서입니다:
- static const
- mutable const
-
Method 선언
- method 선언 뒤에 comments를 두어서는 안 됩니다. (public과 protected methods는 문서에서 설명되어야 합니다. 구현 내 private methods는 class를 쉽게 이해할 수 있도록 만들어야 함)
- 만약 methods를 그룹화하면, methods의 이름을 정렬하고 되도록 return type과 name 사이에 탭 1개(혹은 공백 하나 공간만 나오면 2개)를 두십시오.
또한 이름이 너무 긴 경우를 제외하고는 파라미터들을 정렬하십시오. 예:
/* IO methods */
virtual Int32 Read (IChannel& ic);method의 이름과 파라미터 리스트의 열린 괄호 사이에 최소한 하나의 공백을 두십시오.
Int32 ReadQuotedString (IChannel& ic, bool skipLeadingWhiteSpace = true);
Int32 ReadLine (IChannel& ic);
virtual Int32 Write (OChannel& oc) const;
- 만약 method가 많은 파라미터를 갖고 있으면, 선언을 1개 라인에 맞추지 않습니다. 모든 파라미터는 새로운 라인에 나타나야 합니다.
파라미터의 type과 name은 정렬되어야 합니다. 예:
virtual Vector3D ComputePosition (const Matrix& rot,
Mode mode,
double x,
double y,
double z) const;이 경우, class가 더 눈에 잘 띄게 될 거라고 느끼면 method 선언 뒤에 빈 라인을 둘 수 있습니다.
- template method 선언의 스타일:
template <class U, class V>
inline Pair& operator= (const Pair<U, V>& p);
- 만약 methods를 논리적으로 그룹화한다면, method 그룹들 앞에 간략한 그룹 설명이나 빈 라인을 둘 수 있습니다.
그룹 설명 앞뒤로 빈 라인을 두십시오. 그리고 탭 1개만큼 들여쓰기 하십시오. 예:
/* 비교 methods */
bool operator== (const char* cStr) const;
bool operator!= (const char* cStr) const;/* 변환 methods */
const char* ToCStr (void) const;
const char* ToCStr (Int32 from, Int32 range) const;
- (operator= 같은) 생성자 같은 methods를 생성자 그룹 안에 두십시오. 이 methods에 대한 다른 예제는 다음과 같습니다. (권장 이름을 갖고 있음):
- static String* NewInstance (void); - 새로운 String을 생성하지만 당신은 그것의 주소를 취할 수 있습니다. (생성자와 반대)
- String& Assign (const char* charPtr, Int32 charCount); - operator= 와 같음, 그러나 많은 파라미터들을 가질 수 있습니다.
- virtual String* Clone (void) const; - String을 복사합니다.
- 빈 - void가 아닌 - () 파라미터 리스트를 가진 default 생성자와 소멸자를 작성하십시오.
그렇게 하면 쉽게 찾을 수 있고, 정의에 의해 어떠한 파라미터도 갖고 있지 않다는 것을 강조할 수 있습니다.
(당신은 파라미터 없이 동일한 방식으로 operator 함수들을 작성해야 합니다)
다른 모든 methods와 함수들의 경우, 파라미터가 없으면 당신은 항상 void 키워드를 작성해야 합니다.
- 항상 생성자들의 이름을 정렬해야 합니다. 심지어 explicit 또는 inline 키워드를 사용하더라도 그러합니다. 예:
String ();만약 당신이 자동 변환을 원치 않는다면, 1개의 파라미터를 갖는 생성자 앞에 explicit 키워드를 항상 두어야 합니다. (복사 생성자는 예외)
explicit String (char c);
String (const char* cStr);
inline String (const char* charPtr, Int32 charCount);
explicit String (unsigned char* pStr);
String (const String& source);
- 복사 생성자와 할당 연산자의 필수 파라미터 이름은 source입니다:
String (const String& source);특수한 경우를 제외하고는 항상 const를 작성해야 합니다.
String& operator= (const String& source);
- 당신은 그러한 모든 methods 앞에 virtual 키워드를 작성해서 잘 찾을 수 있게 해야 합니다. 설령 method가 이 속성을 상속하더라도 말입니다.
- abstract virtual methods의 스타일:
virtual void Draw (void) const = 0;= 기호 앞뒤에 공백을 둡니다.
- 많은 수식어 키워드의 경우 (static, virtual, inline, explicit, friend) 순서는 다음과 같습니다:
- static inline
- virtual inline
- explicit inline
- friend inline
- 당신은 class 선언 안에 method 구현을 작성하지 마십시오. (embedded classes의 짧은 methods는 제외)
- 다른 모든 점에서 (파라미터의 배치, default 파라미터 등) methods의 선언은 함수 선언의 규칙을 따릅니다.
-
Embedded classes 및 structures
embedded (유틸리티) classes와 structures는 class의 선언 파트 안에 두어야 합니다. 그리고 1개의 탭만큼 들여쓰기 해야 합니다. 예:
class Vector {이러한 작은 유틸리티 classes의 경우, class 안에 method 구현을 둘 수 있습니다.
private:
// 정의struct RealPair {
double r1; // 1번째 값
double r2; // 2번째 값
RealPair (): r1 (0), r2 (0) {}
RealPair (double val1, double val2) r1 (val1), r2 (val2) {}
};...
};
큰 embedded classes 작성을 피하십시오. 대신 잘 숨겨진 namespace를 만드십시오. (예. String class with the 이름 StringPrivate을 가진 String class에 대하여) 그리고 거기에 embedded class를 두십시오. -
명명 규칙
- Type 이름
- Data member 이름 => Variable 이름을 보십시오.
- Constants
- Class function 이름 => Function 이름을 보십시오.
- Method 이름
-
private 섹션 - 일반적인 레이아웃, 권장사항, 규칙
9. 클래스 구현
-
정적 (클래스) 멤버
Int32 Array::defaultInitialCapacity = 100;data member 이름, '=' 기호, initializer 값을 정렬하도록 하십시오.
const Int32 Array::MaxInitialCapacity = 1000000000;
const double Array::DefaultAllocationUnit = 100.5;
소스 (.cpp) 파일의 시작 부분에 static data members를 선언하십시오.
-
메서드
- method 구현 함수들 간에 2개의 빈 라인을 두십시오.
헤더 파일의 class 끝 뒤에 public inline 함수를 3개의 빈 줄로 두십시오.
- 구현 파일의 methods의 순서는 class 선언에서의 순서와 같아야 합니다.
먼저 생성자를 작성하고, 다른 public methods를 작성합니다. (class 선언에 나온대로)
public methods 뒤에 private methods를 구현하는 것이 실용적입니다.
그리고 protected methods는 가장 논리적으로 맞는 위치에 둡니다.
- 생성자의 스타일
Array::Array (Int32 initialCapacity, Int32 maxSize):ancestor classes의 initializers와 data member를 정렬해야 합니다. ancestor classes의 initializers가 먼저 와야 합니다. 그리고 나서 data members는 상속한 순서대로, 그리고 data member를 선언한 순서대로 나타나야 합니다. data members는 실용성이 있다면 body에서 초기화될 수도 있습니다.
base1 (initialCapacity),
base2 (0, 25),
member1 ("", 40),
member2 (1)
{
// body
}
- Template methods의 스타일:
template <class Type>
inline Int32 Array<Type>::GetSize (void) const
{
// body
}
- method 선언 챕터는 method 헤더의 스타일을 정의합니다.
- 당신은 data members와 함수들에 접근할 때 this가 독특한 역할을 갖고 있을 때에만 this 포인터를 사용해야 합니다. (예. this->x)
이것을 절대로 static members를 위해 사용하지 마십시오.
또한 ancestor class(es)의 members에 접근할 때 사용할 필요가 없습니다.
class의 scope의 바깥에 있는 이름에 접근할 때, 당신은 scope를 사용해야 합니다.
예. (아마도 OS) Sleep () 함수를 이런 식으로 호출하십시오. ::Sleep ()
- 가능하면 data members와 동일한 이름을 가진 method 인자들을 사용하지 마십시오.
만약 다른 논리적 이름을 찾을 수 없다면, data members에 접근할 때에는 꼭 this 포인터를 사용하십시오.
- 할당 연산자를 작성하기 위한 추천하는 스타일 (operator=):
Point2D& Point2D::operator= (const Point2D& source)
{
// source가 NULL인지 테스트함if (this != &source) {
BaseClass1::operator= (source); // 만약 Point2D가 BaseClass1이라는 base class를 갖고 있다면
BaseClass2::operator= (source); // 만약 Point2D가 BaseClass2이라는 base class를 갖고 있다면x = source.x; // 자신의 data member
y = source.y; // 자신의 data member
}return *this;
}
- 다른 점에서 method 구현은 함수 구현의 규칙을 따라야 합니다.
- method 구현 함수들 간에 2개의 빈 라인을 두십시오.
헤더 파일의 class 끝 뒤에 public inline 함수를 3개의 빈 줄로 두십시오.
10. 네임스페이스
void* MemCopy (void* destPtr, const void* srcPtr, unsigned Int32 count);
void* MemMove (void* destPtr, const void* srcPtr, unsigned Int32 count);
void* MemSet (void* destPtr, int filler, unsigned Int32 count);
}
만약 당신이 namespace 안에서 (더 큰) class를 정의한다면, 들여쓰기가 항상 실용적이지는 않습니다. 이 경우 다음 스타일을 사용하십시오:
class Vector {
// body
};
} // namespace Math
함수의 구현에 대하여 namespace를 다시 열지 마십시오.
예. MemCopy 함수의 구현을 namespace 밖이면서 그 위에 작성하십시오:
{
// body
}
이 오류는 class method 구현에서 매우 드물기 때문에 (제공된 method가 class 안에서 앞 부분에 선언되어야 함) 이 경우 당신은 namespace 밖에서 구현하고 namespace를 다시 열거나, using 지시어를 사용할 수 있습니다.
main () 함수를 제외하고 모든 것은 namespace에 들어가야 합니다. (기껏 해야 당신은 주어진 namespace를 열어야 함) 이것은 특히 외부 라이브러리를 사용하는 경우 잠재적인 이름 충돌을 피하기 위해 중요합니다.
구현 파일에서 주로 using namespace X 지시어를 사용하십시오. 실제로 필요한 경우에만 헤더 안에 나타나야 합니다. (예. 만약 열린 namespace 안에 당신의 base module을 갖고 싶을 경우) 헤더 안에 "분리된" using 선언을 사용하지 마십시오. 이 선언들은 헤더를 #include 하는 파일들에 의해 무심코 상속될 수 있습니다.
using 지시어를 사용하는 주된 이유는 주어진 이름(들)이 꽤 자주 나타나는 파일에만 넣기 때문이다. 예. 왜냐하면 그것은 그 영역의 기본 단위(type, constant 등)이기 때문입니다. 약어 두어 개만 사용하는 경우에만 using을 두지 마십시오. 그렇지 않으면 그냥 더 작은 scope 안에 넣으십시오. 예. method 안쪽. 기본적으로 우리는 namespace를 사용하여 모듈과 거기에 선언된 이름을 분리하고 구별하여 수량과 scope 모두에서 using 지시어의 숫자를 제한합니다.
정말로 필요한 곳에서만 namespace alias를 사용하십시오.
11. 전처리기 지시어
// body
#else
// body
#endif
조건부 전처리기 구문 안에 "일반" C++ 코드를 들여쓰기 하지 마십시오; 그러나 다른 전처리기 구문에는 1개의 탭만큼 들여쓰기 하십시오.
2개 화면보다 더 긴 structures 안에 comments를 두십시오. 그러면 일치하는 지시어들을 쉽게 찾을 수 있을 것입니다.
짧은 #ifdef와 #ifndef 지시어를 사용하지 마십시오.
#include와 파일명 사이에 공백을 두십시오:
- #elif를 피하십시오.
- 복잡한 #if expression 변형을 피하십시오. (그러나 #if DEBUG_LEVEL > 5의 경우 필요할 수도 있음)
- 매크로를 피하십시오. inline 그리고/또는 template functions와 methods를 사용하십시오.
- #define을 피하십시오; 대신 const 또는 enum을 사용하십시오.
12. 코멘트 작성하기
- comments의 언어는 English이어야 합니다; 나쁜 말은 피하십시오. 악센트가 붙은 문자도 사용하지 마십시오..
- 당신은 다음 스타일의 comments를 사용해야 합니다; 다른 형태는 금지되어 있습니다: 파일 헤더:
// *****************************************************************************
// One sentence summary
// Module / project name, platform
//
// Namespaces: Contact person:
// DG BM
// LibPart PJ
//
// [SG compatible] - if it really is
// *****************************************************************************이후에만 코드가 올 수 있습니다. #if !defined ARRAY_HPP 지시어도 마찬가지입니다.
- 전체 comment 라인의 길이는 120 글자입니다.
- 당신은 항상 그것이 속해 있는 namespace를 표시해야 합니다. classes의 경우 당신은 owner를 표시해야 할 수도 있습니다.
- 당신은 항상 class 선언 안의 data members 뒤에 comments를 두어야 합니다: 가능하면 data member가 사용된 동일한 상태에서.
global 변수들에 대해서는 동일한 것이 유효합니다. class 선언에서는 methods에 comments를 붙이지 않습니다.
- 사용하지 않는 함수 파라미터들에는 /* ... */ 라는 comment를 붙이십시오;
#pragma unused 지시어를 사용하지 마십시오.
만약 릴리즈 빌드 안에서 필요할 경우, 대신 UNUSED_PARAMETER 매크로를 사용하십시오.
- 모듈의 public이 아닌 함수들의 경우, 당신은 항상 non-trivial 파라미터들과 리턴 값의 의미를 설명해야 합니다.
예. 인덱스의 경우 항상 범위를 표시함. (0 또는 1부터 시작, 그 외 특수한 값, 등) 예:
// -----------------------------------------------------------------------------
// Function: Reads ...
//
// inHandle: Contains the text. If nil read from 'inFile'
// inFile: Input file description
//
// return value: OK or ERROR
// -----------------------------------------------------------------------------OSErr ReadTemplate (Handle inHandle, FileDefRec inFile)
{
...
}
- 만약 뭔가를 마치지 못했다면 당신은 항상 3개의 느낌표로 표시해 두어야 합니다:
n = 5; // !!! st; It will come from database
- 당신은 또한 현재 탭 깊이에서 시작하여 소스 코드의 라인 간에 선택적인 섹션 설명을 넣을 수 있습니다.
세미콜론 이후에 탭을 넣고 그 소스 라인의 끝에 짧은 설명을 넣을 수도 있습니다.
혹시 내용이 많으면 comments를 탭으로 정렬할 수 있습니다. 예:
n = 5;당신은 항상 comments를 추가해야 합니다:// Searching will follow
for (i = 0; i < n; i++) {
if (a[i].GetCode () == pattern) {
b[i].SetValue (a[i].GetValue ()); // Found
}
}
- 특이한 해결책의 경우. (break 밖으로 나가기, 빈 loop 등)
- 만약 comment가 없으면 다른 사람이 코드를 이해하는 데 너무 오래 걸릴 경우.
- 만약 다른 사람에게 어떤 것이 금지되거나 추천되지 않는 경우.
comments가 코드의 흐름을 깨뜨리지 않도록 하십시오. 그리고 코드를 해제하지 마십시오.
13. 파일 structure
- 항상 모든 파일 안의 탭들은 4개의 공백을 사용해야 합니다.
- 라인의 최대 길이는 120 글자입니다. 구문(함수, method의 body)은 이 길이에 가까이 가서도 안 됩니다.
- 헤더 파일의 body는 다음 전처리기 structure와 함께 괄호로 싸야 합니다.
이름은 namespace 식별자, 파일 이름, HPP 접미사로 구성되어 있으며 대문자와 그것을 분리하는 밑줄(_)로 되어 있습니다. 예:
#if !defined IO_LOCATION_HPP만약 헤더 파일의 이름이 Location.hpp이고 namespace가 IO라면,
#define IO_LOCATION_HPP// body
#endif
이것은 전처리기 structures의 특수한 경우입니다; 라인을 안쪽으로 들여쓰지 하지 마십시오.
- 모든 #include를 소스 파일의 시작 부분에 작성하십시오. 헤더 파일의 경우 모든 Forward declarations 앞에 두십시오. 예:
// -------------------------------- Includes ---------------------------------#include "Base.hpp"
#include "Array.hpp"
#include "Stack.hpp"
// ---------------------------- Forward declarations ------------------------------
class SymbolSet;
- 파일 이름의 최대 길이는 31 글자입니다.
파일 이름은 대소문자를 구분합니다. 그리고 확장자는 .cpp와 .hpp, 또는 .c와 .h를 사용하는데 이것은 순수한 C 코드에서 사용될 수 있는지 여부에 따라 달라집니다.
파일 이름은 모든 레퍼런스에서 대소문자를 구분해서 주어져야 합니다. (#include, makefile)
- #include 안에서 상대 경로를 사용할 수 있습니다. 그러나 ".." 경로 컴포넌트는 사용할 수 없습니다.
게다가 module roots를 참조할 수 없습니다. (예: #include "GSRoot/Array.hpp")
오직 안에 있는 서브 디렉토리만 접근 가능합니다.
- 되도록 모든 class는 자신의 header와 source 파일을 갖고 있어야 합니다. 그러나 밀접하게 관련된 classes를 많이 갖고 있다면 한 파일 안에 두십시오.
template class와 특화된 버전들도 역시 한 파일 안에 두어야 합니다.
반면, base class와 그것의 자손들은 한 파일 안에 두지 마십시오.
- 모든 헤더 파일 #include 는 오직 class 선언이 필요한 것만 하십시오: ancestor classes와 data member types.
만약 type이 포인터, 레퍼런스, 또는 class의 method return 값으로 사용된다면, #include 대신 forward declarations를 사용하십시오.
이렇게 하면 의존성 수와 include 되는, (그리고 컴파일 시간에 열리는) 파일 수를 줄일 수 있습니다.
- 각 서브 시스템의 파일(module, package)들은 분리된 서브 디렉토리 안에 있어야 합니다.