C++ 코딩 습관, Tour 1.10 Advice 정리

C++ 코딩 습관은 새 문법 나열이 아니라, 주석·초기화·이름·범위·상수를 일관되게 쓰는 규칙 모음입니다.

이 글은 Bjarne Stroustrup, A Tour of C++ 3rd ed. §1.10 Advice 공부 노트를 2026-09-12 기준으로 정리한 일반 설명이며, 표준·구현·개정판에 따라 세부 서술은 달라질 수 있습니다. A Tour of C++ Ch.1(The Basics) 마무리이며, 다음은 Ch.2 User-Defined Types입니다.

주석은 왜만 남기나?

한 줄 답: 코드가 이미 말하는 것을 반복하지 않고, 코드에 드러나지 않는 의도(왜)를 설명하기 위해서입니다.

주석 vs 코드 가독성

주석 vs 코드 가독성

아래 두 주석은 같은 줄에 붙었지만 의미가 다릅니다. 첫 번째는 코드가 이미 말하는 내용을 그대로 반복하므로 빼는 편이 낫고, 두 번째는 코드만으로는 알 수 없는 의도를 담고 있어 남길 가치가 있습니다.

++i;          // i를 1 증가        -> 빼라. 코드가 이미 말함
++i;          // 한 칸 이동해 충돌을 피한다  -> 남겨라. 왜인지 적음

한 줄로 정리하면 다음과 같습니다: 코드는 무엇을, 주석은 왜.

이 규칙과 같이 가는 간결한 코드 습관도 있습니다. 복잡한 수식은 쪼개서 적고, 선언 하나에는 이름 하나만 둡니다. 흔하게 반복해서 쓰는 로컬 변수는 짧은 이름으로, 드물게 쓰거나 멀리서 참조하는 이름은 길게 짓습니다. 비슷하게 생긴 이름이나 ALL_CAPS 이름은 피하고, 들여쓰기는 한 스타일로 유지합니다. 함수 하나는 논리적으로 한 가지 일만 하도록 짧게 유지하고, 이름 자체에 의미를 담습니다. 이 항목들의 출처는 C++ Core Guidelines의 일부이며, [CG: ES.23]과 같은 표시는 본문에서 참고용 가이드 번호로만 언급합니다.

내장 기능만 쓰지 말라는 뜻은?

한 줄 답: 저수준 배열이나 포인터를 직접 쓰기보다, 검증된 표준 라이브러리를 적극적으로 활용하라는 의미입니다.

처음부터 언어의 모든 세부 사항을 알아야 하는 것은 아닙니다. 당황할 필요 없이, 좋은 프로그램을 쓰기 위해 필요한 만큼 익혀가면 됩니다. 이때 초점은 언어 기능을 하나씩 수집하는 데 두기보다, 문제를 푸는 프로그래밍 기법에 두는 편이 낫습니다.

int*나 직접 다루는 배열보다 std::string, std::vector 같은 표준 라이브러리 요소를 거쳐 쓰는 경우가 많습니다. 이런 요소는 이미 검증되어 있고, 범위 관리나 메모리 해제 같은 세부 사항을 대신 처리해 줍니다. 필요한 기능은 #include로 가져오거나, 가능하다면 import로 가져다 쓰는 편을 선호합니다. 언어 정의에 대한 최종 권위는 ISO C++ 표준에 있다는 점도 함께 기억해 둘 만합니다.

함수·상수·하드웨어 값은 어떻게 다루나?

한 줄 답: 의미 있는 동작은 함수로 묶고, 상수는 constexprconsteval로 지정하며, 좁히는 변환을 피해야 합니다.

의미 있는 동작은 이름 있는 함수로 묶습니다. 하는 일이 같고 다루는 타입만 다르면, 함수를 여러 개 따로 만들기보다 오버로드를 사용합니다. 값이 컴파일 타임에 계산될 수 있으면 constexpr을 붙이고, 반드시 컴파일 타임에 계산되어야 하는 경우에는 consteval을 사용합니다. 두 경우 모두 부작용이 없어야 합니다.

기본 연산이 실제 하드웨어에서 어떻게 실행되는지 이해하는 것도 §1.9와 이어지는 습관입니다. 큰 숫자를 적을 때는 1'000'000처럼 자릿수 구분 기호를 넣어 가독성을 높일 수 있습니다.

좁히는 변환은 피해야 합니다. 예를 들어 double 값을 int에 대입하면 소수 부분이 조용히 잘려 나갑니다.

int n = 7.9;    // 좁혀서 7. 피하라
int n {7.9};    // {}는 보통 이런 좁히는 변환을 에러로 막는다

매직 넘버를 코드 곳곳에 흩어 두는 대신 constexpr int max_attempts = 3;처럼 이름 있는 상수로 정의합니다. 값을 바꿀 필요가 없다면 constconstexpr로 불변으로 만들고, 변수의 범위는 필요한 만큼만 최소로 유지합니다.

초기화와 auto는 어떻게 쓰나?

한 줄 답: 초기화되지 않은 변수를 만들지 말고, {} 초기화나 타입 추론을 위한 auto를 적극적으로 활용해야 합니다.

변수에 넣을 값이 아직 없다면, 그 시점에는 변수를 선언하지 않는 편이 낫습니다. 아래처럼 값 없이 선언부터 해두고 나중에 대입하는 방식은 피하는 편이 좋습니다.

int n;          // 비권장. 아직 값 없음
n = v.size();

auto n = v.size();   // 권장

이름 있는 타입을 쓸 때는 {} 초기화를 선호합니다. 타입 이름을 반복해서 적는 것이 번거로울 때는 위 예시처럼 auto로 타입을 추론하게 둘 수 있습니다.

if 조건문 안에서 변수를 선언하고 곧바로 검사하는 짧은 형태도 선호되는 스타일입니다. 아래 형태는 n을 조건문 블록 범위로 제한하면서, 결과를 0이나 nullptr과 비교하는 것과 같은 효과를 냅니다.

if (auto n = v.size()) { /* ... */ }   // 권장

루프·포인터·unsigned는?

한 줄 답: 루프는 range-for를, 포인터는 단순하게 nullptr을 사용하며, unsigned는 인덱스가 아닌 비트 조작에만 써야 합니다.

습관 보드 (auto · {} · range-for · nullptr)

습관 보드 (auto · {} · range-for · nullptr)

인덱스를 직접 세면서 도는 기본 for문보다, 컨테이너의 원소를 직접 순회하는 range-for 루프를 선호합니다.

for (char ch : act) { /* ... */ }

포인터를 다룰 때는 복잡하게 쓰지 않도록 하고, 빈 포인터를 나타낼 때는 0이나 NULL 대신 nullptr을 사용합니다. unsigned 타입은 개수를 세거나 인덱스를 나타낼 때 쓰지 않고, 비트 단위 연산이 실제로 필요할 때만 제한적으로 사용합니다.

FAQ

Q. if (auto n = v.size()) 구문은 어떤 의미입니까?

결과가 0이 아닌지(n != 0)를 검사하는 것과 같으며, 코드가 짧아지고 변수 n의 범위를 조건문 블록 안으로 제한할 수 있어 권장됩니다.

Q. 변수를 미리 선언해두고 나중에 값을 대입(int n; n = v.size();)하면 안 됩니까?

초기화 없이 선언만 해두면 대입 전에 실수로 값을 읽어 미정의 동작이 발생할 위험이 있으므로 피해야 합니다.

Q. 주석에 “x에 y를 넣는다”라고 쓰면 안 되는 이유는 무엇입니까?

코드가 수행하는 동작 자체는 코드만 읽어도 알 수 있습니다. 좋은 주석은 코드가 말해주지 않는 의도, 즉 왜 그렇게 하는지를 설명해야 합니다.

Q. 루프 인덱스나 개수를 셀 때 unsigned를 피하라는 이유는 무엇입니까?

타입 간 연산에서 예상치 못한 형변환이나 값이 음수 방향으로 넘어가는 문제로 버그가 발생하기 쉽기 때문에, unsigned는 비트를 다룰 때만 사용하는 편을 권장합니다.

출처