일반 권장 사항
포매팅
clang-format이 자동으로 처리합니다.
2. 들여쓰기는 공백 4칸입니다. 탭 키 입력 시 공백 4개가 삽입되도록 개발 환경을 설정하십시오.
3. 여는 중괄호와 닫는 중괄호는 각각 별도의 줄에 작성해야 합니다.
statement인 경우, 한 줄로 작성할 수 있습니다. 중괄호 양쪽에 공백을 넣으십시오(줄 끝 공백은 제외).
if, for, while 및 기타 표현식에서는 여는 괄호 앞에 공백을 삽입합니다(함수 호출과는 달리).
+, -, *, /, %, …) 및 삼항 연산자(ternary operator) ?: 앞뒤에 공백을 추가하십시오.
., -> 연산자 앞뒤에 공백을 사용하지 마십시오.
필요한 경우 연산자를 다음 줄로 내려쓸 수 있습니다. 이 경우 연산자 앞의 들여쓰기가 늘어납니다.
11. 단항 연산자(--, ++, *, &, …)와 인수 사이에 공백을 넣지 마십시오.
12. 쉼표 뒤에는 공백을 넣되, 앞에는 넣지 마십시오. for 표현식 내부의 세미콜론에도 동일한 규칙이 적용됩니다.
13. [] 연산자 앞뒤에 공백을 사용하지 마십시오.
14. template <...> 표현식에서 template과 < 사이에는 공백을 넣고, < 뒤와 > 앞에는 공백을 넣지 마십시오.
public, private, protected는 class/struct와 동일한 들여쓰기 수준으로 작성하고, 나머지 코드는 한 단계 들여쓰기하십시오.
namespace가 사용되고 특별히 중요한 다른 내용이 없다면, namespace 내부에 들여쓰기(offset)를 할 필요가 없습니다.
17. if, for, while 또는 다른 표현식의 블록이 단일 statement로 구성된 경우, 중괄호는 생략할 수 있습니다. 대신 statement를 별도의 줄에 작성하십시오. 이 규칙은 중첩된 if, for, while, …에도 동일하게 적용됩니다.
단, 내부 statement에 중괄호나 else가 포함된 경우 외부 블록도 중괄호로 작성해야 합니다.
const는 type name 앞에 작성해야 합니다.
*와 & 기호 앞뒤를 공백으로 구분해야 합니다.
using 키워드로 별칭을 지정하십시오.
즉, Template 매개변수는 using에서만 지정하며, 코드에는 반복해서 작성하지 않습니다.
using은 함수 내부처럼 로컬 범위에서 선언할 수 있습니다.
Comments
///로 시작하고, 여러 줄 Comments는 /**로 시작합니다. 이러한 Comments는 “문서화” Comments로 간주됩니다.
참고: 이러한 Comments에서 Doxygen을 사용해 문서를 생성할 수 있습니다. 하지만 IDE에서 코드를 탐색하는 편이 더 편리하므로 일반적으로 Doxygen은 사용하지 않습니다.
9. 여러 줄 Comments의 시작과 끝에는 빈 줄이 있으면 안 됩니다(여러 줄 Comments를 닫는 줄은 제외).
10. 코드를 Comments 처리할 때는 “문서화” Comments가 아니라 일반 Comments를 사용하십시오.
11. 커밋하기 전에 Comments 처리한 코드 부분은 삭제하십시오.
12. Comments나 코드에 욕설을 사용하지 마십시오.
13. 대문자를 사용하지 마십시오. 문장 부호를 과도하게 사용하지 마십시오.
이름
using의 이름은 클래스와 같은 방식으로 지정합니다.
5. Template 유형 인수의 이름: 단순한 경우에는 T, U, T1, T2를 사용합니다.
더 복잡한 경우에는 클래스 이름 규칙을 따르거나 접두사 T를 추가합니다.
N을 사용합니다.
I 접두사를 붙일 수 있습니다.
define와 전역 상수의 이름에는 밑줄로 구분한 ALL_CAPS를 사용합니다.
- 변수 이름에서는 약어를 소문자로 사용해야 합니다.
mysql_connection(mySQL_connection아님) - 클래스와 함수 이름에서는 약어의 대문자를 유지합니다.
MySQLConnection(MySqlConnection아님)
enum의 상수에는 첫 글자를 대문자로 하는 CamelCase를 사용합니다. ALL_CAPS도 사용할 수 있습니다. enum이 지역 범위가 아니면 enum class를 사용합니다.
AST, SQL.
NVDH는 안 됩니다(임의의 문자 나열).
줄인 형태가 일반적으로 쓰인다면 완전한 단어가 아니어도 허용됩니다.
주석에 전체 이름이 함께 적혀 있다면 약어를 사용할 수도 있습니다.
17. C++ 소스 코드 파일 이름은 .cpp 확장자를 사용해야 합니다. 헤더 파일은 .h 확장자를 사용해야 합니다.
코드 작성 방법
delete)는 라이브러리 코드에서만 사용할 수 있습니다.
라이브러리 코드에서 delete 연산자는 소멸자 안에서만 사용할 수 있습니다.
애플리케이션 코드에서는 메모리를 소유한 객체가 직접 해제해야 합니다.
예시:
- 가장 쉬운 방법은 객체를 스택에 두거나 다른 클래스의 멤버로 만드는 것입니다.
- 작은 객체가 매우 많다면 컨테이너를 사용합니다.
- 힙에 있는 소수의 객체를 자동으로 해제하려면
shared_ptr/unique_ptr를 사용합니다.
RAII를 사용하고, 위 내용을 참고하십시오.
3. 오류 처리.
예외를 사용합니다. 대부분의 경우 예외를 발생시키기만 하면 되며, 이를 잡을 필요는 없습니다(RAII 때문입니다).
오프라인 데이터 처리 애플리케이션에서는 예외를 잡지 않아도 되는 경우가 많습니다.
사용자 요청을 처리하는 서버에서는 일반적으로 연결 핸들러의 최상위 수준에서 예외를 잡는 것만으로 충분합니다.
스레드 함수에서는 join 후 메인 스레드에서 다시 예외를 발생시킬 수 있도록 모든 예외를 잡아 보관해야 합니다.
errno를 사용하는 함수는 항상 반환 결과를 확인하고, 오류가 발생하면 예외를 발생시키십시오.
- 예외로 이어질 수 있는 모든 작업을 미리 수행하는 함수(
done()또는finalize())를 만드십시오. 이 함수가 호출되었다면, 이후 소멸자에서는 예외가 발생하지 않아야 합니다. - 지나치게 복잡한 작업(예: 네트워크를 통한 메시지 전송)은 클래스 사용자가 소멸 전에 호출해야 하는 별도의 메서드로 분리할 수 있습니다.
- 소멸자에서 예외가 발생하면, 이를 숨기기보다는 로그로 남기는 편이 낫습니다(로거를 사용할 수 있다면).
- 단순한 애플리케이션에서는 예외 처리를 위해
std::terminate(C++11에서 기본적으로 적용되는noexcept의 경우)에 의존해도 무방합니다.
- 단일 CPU 코어에서 가능한 한 최고의 성능을 내도록 하십시오. 그런 다음 필요하면 코드를 병렬화할 수 있습니다.
- 요청 처리에는 스레드 풀을 사용하십시오. 지금까지는 사용자 공간 Context 전환이 필요한 작업이 없었습니다.
fork를 사용하지 않습니다.
8. 스레드 동기화.
서로 다른 스레드가 서로 다른 메모리 셀(더 나아가 서로 다른 캐시 라인)을 사용하도록 하고, 스레드 동기화를 전혀 사용하지 않는 것(joinAll 제외)이 가능한 경우가 많습니다.
동기화가 필요하다면 대부분의 경우 lock_guard와 함께 뮤텍스를 사용하면 충분합니다.
그 밖의 경우에는 시스템 동기화 프리미티브를 사용하십시오. busy wait는 사용하지 마십시오.
원자적 연산은 가장 단순한 경우에만 사용해야 합니다.
잠금 없는(lock-free) 데이터 구조는 해당 분야가 주된 전문 분야가 아니라면 구현하려고 하지 마십시오.
9. 포인터와 참조.
대부분의 경우 참조를 우선해서 사용하십시오.
10. const.
상수 참조, 상수를 가리키는 포인터, const_iterator, const 메서드를 사용하십시오.
const를 기본값으로 생각하고, 필요한 경우에만 non-const를 사용하십시오.
변수를 값으로 전달할 때는 const를 사용하는 것이 대체로 의미가 없습니다.
11. unsigned.
필요한 경우 unsigned를 사용하십시오.
12. 숫자 타입.
UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64 타입과 size_t, ssize_t, ptrdiff_t를 사용하십시오.
숫자에는 다음 타입을 사용하지 마십시오: signed/unsigned long, long long, short, signed/unsigned char, char.
13. 인수 전달.
이동될 복잡한 값은 값으로 전달하고 std::move를 사용하십시오. 루프에서 값을 갱신해야 한다면 참조로 전달하십시오.
함수가 힙에서 생성된 객체의 소유권을 넘겨받는다면, 인수 타입은 shared_ptr 또는 unique_ptr로 지정하십시오.
14. 반환 값.
대부분의 경우 return만 사용하면 됩니다. return std::move(res)는 작성하지 마십시오.
함수가 힙에 객체를 할당해 반환한다면 shared_ptr 또는 unique_ptr를 사용하십시오.
드문 경우에는(루프에서 값을 갱신하는 경우) 인수를 통해 값을 반환해야 할 수 있습니다. 이 경우 인수는 참조여야 합니다.
namespace.
애플리케이션 코드에 별도의 namespace를 사용할 필요는 없습니다.
작은 라이브러리에서도 마찬가지로 필요하지 않습니다.
중대형 라이브러리에서는 모든 것을 namespace 안에 넣으십시오.
라이브러리의 .h 파일에서는 애플리케이션 코드에 필요하지 않은 구현 세부 사항을 숨기기 위해 namespace detail을 사용할 수 있습니다.
.cpp 파일에서는 심볼을 숨기기 위해 static 또는 익명 namespace를 사용할 수 있습니다.
또한 관련 이름이 외부 namespace로 노출되지 않도록 enum에 namespace를 사용할 수도 있습니다(하지만 enum class를 사용하는 편이 더 낫습니다).
16. 지연 초기화.
초기화에 인수가 필요하다면 일반적으로 기본 생성자를 작성하지 않는 편이 좋습니다.
나중에 초기화를 지연해야 한다면 유효하지 않은 객체를 생성하는 기본 생성자를 추가할 수 있습니다. 또는 객체 수가 적다면 shared_ptr/unique_ptr를 사용할 수 있습니다.
std::string과 char *를 사용하십시오. std::wstring과 wchar_t는 사용하지 마십시오.
19. 로깅.
코드 전반의 예시를 참조하십시오.
커밋하기 전에 의미 없는 로깅, 디버그 로깅, 그 밖의 모든 디버그 출력을 삭제하십시오.
루프 안에서의 로깅은 Trace 수준에서도 피해야 합니다.
로그는 어떤 로깅 수준에서도 읽기 쉬워야 합니다.
로깅은 대체로 애플리케이션 코드에서만 사용해야 합니다.
로그 메시지는 영어로 작성해야 합니다.
로그는 가능하면 시스템 관리자가 이해할 수 있어야 합니다.
로그에 욕설을 사용하지 마십시오.
로그에는 UTF-8 인코딩을 사용하십시오. 드문 경우에는 로그에 비ASCII 문자를 사용할 수 있습니다.
20. 입출력.
애플리케이션 성능에 중요한 내부 루프에서는 iostreams를 사용하지 마십시오(stringstream은 절대 사용하지 마십시오).
대신 DB/IO 라이브러리를 사용하십시오.
21. 날짜와 시간.
DateLUT 라이브러리를 참조하십시오.
22. include.
include 가드 대신 항상 #pragma once를 사용하십시오.
23. using.
using namespace는 사용하지 않습니다. 특정 대상에 대해서만 using을 사용할 수 있습니다. 다만 클래스나 함수 내부의 로컬 범위에서만 사용하십시오.
24. 꼭 필요한 경우가 아니면 함수에 trailing return type을 사용하지 마십시오.
virtual을 쓰고, 파생 클래스에서는 virtual 대신 override를 사용합니다.
C++에서 사용하지 않는 기능
플랫폼
clang. 이 문서를 작성하는 시점(2025년 3월) 기준으로 코드는 clang 버전 >= 19로 컴파일합니다.
표준 라이브러리(libc++)를 사용합니다.
4. OS: Linux Ubuntu, Precise 이상.
5. 코드는 x86_64 CPU 아키텍처용으로 작성합니다.
CPU 명령어 집합은 서버에서 지원하는 최소 집합을 기준으로 합니다. 현재는 SSE 4.2입니다.
6. 일부 예외를 제외하고 -Wall -Wextra -Werror -Weverything 컴파일 플래그를 사용합니다.
7. 정적 연결이 어려운 라이브러리를 제외한 모든 라이브러리는 정적 링크를 사용합니다(ldd 명령의 출력 참조).
8. 코드는 릴리스 설정으로 개발하고 디버깅합니다.
도구
gdb, valgrind (memcheck), strace, -fsanitize=..., 또는 tcmalloc_minimal_debug를 사용합니다.
3. 프로파일링에는 Linux Perf, valgrind (callgrind), 또는 strace -cf를 사용합니다.
4. 소스 코드는 Git으로 관리됩니다.
5. 빌드에는 CMake를 사용합니다.
6. 프로그램은 deb 패키지로 배포됩니다.
7. master에 커밋할 때는 빌드가 깨지지 않도록 해야 합니다.
다만 일부 선별된 리비전만 정상적으로 동작하는 것으로 간주됩니다.
8. 코드가 아직 완전히 준비되지 않았더라도 가능한 한 자주 커밋하십시오.
이를 위해 브랜치를 사용하십시오.
master 브랜치의 코드가 아직 빌드되지 않는다면, push 전에 빌드 대상에서 제외하십시오. 며칠 안에 이를 마무리하거나 제거해야 합니다.
9. 단순하지 않은 변경의 경우 브랜치를 사용하고 서버에 공개하십시오.
10. 사용되지 않는 코드는 리포지토리에서 제거됩니다.
라이브러리
boost 및 Poco 프레임워크도 사용합니다.
2. OS 패키지의 라이브러리는 사용할 수 없습니다. 미리 설치된 라이브러리도 사용할 수 없습니다. 모든 라이브러리는 contrib 디렉터리에 소스 코드 형태로 두고 ClickHouse와 함께 빌드해야 합니다. 자세한 내용은 새로운 서드파티 라이브러리 추가 가이드라인을 참조하십시오.
3. 항상 이미 사용 중인 라이브러리를 우선적으로 선택합니다.
일반 권장 사항
using을 사용하십시오.
5. 가능하면 복사 생성자, 대입 연산자, 소멸자(클래스에 가상 함수가 하나 이상 있는 경우의 가상 소멸자는 제외), 이동 생성자, 이동 대입 연산자는 작성하지 마십시오. 즉, 컴파일러가 생성한 함수가 올바르게 동작해야 합니다. default를 사용할 수 있습니다.
6. 코드는 단순할수록 좋습니다. 가능하면 코드의 규모를 줄이십시오.
추가 권장 사항
stddef.h의 타입에 std::를 명시적으로 붙이는 것
은 권장하지 않습니다. 즉, 더 짧기 때문에 std::size_t 대신 size_t를 사용하는 것을 권장합니다.
std::를 붙여도 괜찮습니다.
2. 표준 C 라이브러리 함수에 std::를 명시적으로 붙이는 것
은 권장하지 않습니다. 즉, std::memcpy 대신 memcpy를 사용하십시오.
그 이유는 memmem처럼 비슷한 비표준 함수도 있기 때문입니다. 이런 함수도 때때로 사용합니다. 이러한 함수는 namespace std에 존재하지 않습니다.
어디서나 memcpy 대신 std::memcpy를 사용하면, std::가 없는 memmem이 어색해 보일 수 있습니다.
그래도 원한다면 std::를 계속 사용할 수 있습니다.
3. 같은 함수가 표준 C++ 라이브러리에도 있을 때 C 함수를 사용하는 것.
더 효율적이라면 허용됩니다.
예를 들어, 큰 메모리 청크를 복사할 때는 std::copy 대신 memcpy를 사용하십시오.
4. 여러 줄 함수 인수.
다음 줄바꿈 스타일은 모두 허용됩니다: