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입니다.
주석은 왜만 남기나?
한 줄 답: 코드가 이미 말하는 것을 반복하지 않고, 코드에 드러나지 않는 의도(왜)를 설명하기 위해서입니다.

아래 두 주석은 같은 줄에 붙었지만 의미가 다릅니다. 첫 번째는 코드가 이미 말하는 내용을 그대로 반복하므로 빼는 편이 낫고, 두 번째는 코드만으로는 알 수 없는 의도를 담고 있어 남길 가치가 있습니다.
++i; // i를 1 증가 -> 빼라. 코드가 이미 말함
++i; // 한 칸 이동해 충돌을 피한다 -> 남겨라. 왜인지 적음
한 줄로 정리하면 다음과 같습니다: 코드는 무엇을, 주석은 왜.
이 규칙과 같이 가는 간결한 코드 습관도 있습니다. 복잡한 수식은 쪼개서 적고, 선언 하나에는 이름 하나만 둡니다. 흔하게 반복해서 쓰는 로컬 변수는 짧은 이름으로, 드물게 쓰거나 멀리서 참조하는 이름은 길게 짓습니다. 비슷하게 생긴 이름이나 ALL_CAPS 이름은 피하고, 들여쓰기는 한 스타일로 유지합니다. 함수 하나는 논리적으로 한 가지 일만 하도록 짧게 유지하고, 이름 자체에 의미를 담습니다. 이 항목들의 출처는 C++ Core Guidelines의 일부이며, [CG: ES.23]과 같은 표시는 본문에서 참고용 가이드 번호로만 언급합니다.
내장 기능만 쓰지 말라는 뜻은?
한 줄 답: 저수준 배열이나 포인터를 직접 쓰기보다, 검증된 표준 라이브러리를 적극적으로 활용하라는 의미입니다.
처음부터 언어의 모든 세부 사항을 알아야 하는 것은 아닙니다. 당황할 필요 없이, 좋은 프로그램을 쓰기 위해 필요한 만큼 익혀가면 됩니다. 이때 초점은 언어 기능을 하나씩 수집하는 데 두기보다, 문제를 푸는 프로그래밍 기법에 두는 편이 낫습니다.
int*나 직접 다루는 배열보다 std::string, std::vector 같은 표준 라이브러리 요소를 거쳐 쓰는 경우가 많습니다. 이런 요소는 이미 검증되어 있고, 범위 관리나 메모리 해제 같은 세부 사항을 대신 처리해 줍니다. 필요한 기능은 #include로 가져오거나, 가능하다면 import로 가져다 쓰는 편을 선호합니다. 언어 정의에 대한 최종 권위는 ISO C++ 표준에 있다는 점도 함께 기억해 둘 만합니다.
함수·상수·하드웨어 값은 어떻게 다루나?
한 줄 답: 의미 있는 동작은 함수로 묶고, 상수는 constexpr나 consteval로 지정하며, 좁히는 변환을 피해야 합니다.
의미 있는 동작은 이름 있는 함수로 묶습니다. 하는 일이 같고 다루는 타입만 다르면, 함수를 여러 개 따로 만들기보다 오버로드를 사용합니다. 값이 컴파일 타임에 계산될 수 있으면 constexpr을 붙이고, 반드시 컴파일 타임에 계산되어야 하는 경우에는 consteval을 사용합니다. 두 경우 모두 부작용이 없어야 합니다.
기본 연산이 실제 하드웨어에서 어떻게 실행되는지 이해하는 것도 §1.9와 이어지는 습관입니다. 큰 숫자를 적을 때는 1'000'000처럼 자릿수 구분 기호를 넣어 가독성을 높일 수 있습니다.
좁히는 변환은 피해야 합니다. 예를 들어 double 값을 int에 대입하면 소수 부분이 조용히 잘려 나갑니다.
int n = 7.9; // 좁혀서 7. 피하라
int n {7.9}; // {}는 보통 이런 좁히는 변환을 에러로 막는다
매직 넘버를 코드 곳곳에 흩어 두는 대신 constexpr int max_attempts = 3;처럼 이름 있는 상수로 정의합니다. 값을 바꿀 필요가 없다면 const나 constexpr로 불변으로 만들고, 변수의 범위는 필요한 만큼만 최소로 유지합니다.
초기화와 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는 인덱스가 아닌 비트 조작에만 써야 합니다.

인덱스를 직접 세면서 도는 기본 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는 비트를 다룰 때만 사용하는 편을 권장합니다.
출처
- Bjarne Stroustrup, A Tour of C++ (3rd ed.), §1.10 Advice
- C++ Core Guidelines
- cppreference — auto
- cppreference — range-based for loop
C++ coding habits are not just a list of new syntax; they are a set of rules for consistently handling comments, initialization, naming, scope, and constants.
This is a general explanation summarized from study notes on Bjarne Stroustrup's A Tour of C++, 3rd ed., §1.10 Advice, as of 2026-09-12. Details may vary by standard version, implementation, or edition. This post concludes Chapter 1 (The Basics) of A Tour of C++; the next chapter is Ch.2 User-Defined Types.
Why should comments explain only the "why"?
Short answer: Comments should not repeat what the code already says; they should explain the intent behind the code that is not otherwise visible.

The two comments below sit on similar lines but differ in value. The first simply restates what the code already expresses, so it is better removed. The second captures intent that cannot be read from the code alone, so it is worth keeping.
++i; // increase i by 1 -> remove: the code already says this
++i; // move one step to avoid a collision -> keep: explains why
The rule can be summarized in one line: code says what, comments say why.
Several related habits for concise code go along with this rule. Split complex expressions into smaller pieces, and give each declaration exactly one name. Keep common, frequently used local names short, and give rare or distantly referenced names longer, more descriptive forms. Avoid similar-looking names and ALL_CAPS identifiers, and keep indentation consistent throughout. A function should do one logical thing, stay short, and carry meaning in its own name. These items originate from the C++ Core Guidelines, and references such as [CG: ES.23] are mentioned here only as guideline numbers, not as full quoted text.
What does "don't just use built-in features" mean?
Short answer: Prefer well-tested standard library components over raw, low-level arrays or pointers.
There is no need to know every language detail from the start. Rather than feeling overwhelmed, it is enough to pick up what is needed to write good programs over time. The focus should be on programming technique, not on collecting language features one by one.
Standard library components such as std::string and std::vector are generally preferred over a raw int* or a directly managed array. These components are already well tested and take care of details such as range management and memory release. Needed functionality is usually pulled in with #include, or with import where that is available. It is also worth remembering that the final authority on the language definition is the ISO C++ standard.
How should functions, constants, and hardware-related values be handled?
Short answer: Group meaningful actions into functions, enforce constants with constexpr or consteval, and avoid narrowing conversions.
Meaningful operations should be grouped into named functions. When several functions do the same job for different types, overloading is preferred over writing separate functions. A value that can be computed at compile time should be marked constexpr; a value that must be computed at compile time should be marked consteval. Both must be free of side effects.
Understanding how basic operations run on actual hardware is a habit that connects back to §1.9. For large numbers, digit separators such as 1'000'000 can improve readability.
Narrowing conversions should be avoided. For example, assigning a double value to an int silently truncates the fractional part.
int n = 7.9; // narrows to 7 -- avoid this
int n {7.9}; // {} initialization normally turns this narrowing into an error
Instead of scattering magic numbers through the code, define named constants such as constexpr int max_attempts = 3;. If a value never needs to change, make it immutable with const or constexpr, and keep every variable's scope as small as it needs to be.
How should initialization and auto be used?
Short answer: Never leave a variable uninitialized, and rely on {} initialization or auto for type deduction.
If there is no value ready for a variable yet, it is better not to declare that variable at that point. The pattern below, which declares a variable without a value and assigns it later, is one to avoid.
int n; // not recommended: no value yet
n = v.size();
auto n = v.size(); // recommended
{} initialization is preferred for named types. When repeating a type name feels unnecessary, auto can be used to let the compiler deduce the type, as in the example above.
Declaring and testing a variable directly inside an if condition is also a preferred style. The form below scopes n to the conditional block while behaving the same as comparing the result against 0 or nullptr.
if (auto n = v.size()) { /* ... */ } // recommended
What about loops, pointers, and unsigned?
Short answer: Prefer range-for loops, use nullptr for pointers, and reserve unsigned strictly for bit manipulation.

A range-for loop that iterates directly over a container's elements is preferred over a basic for loop that manually tracks an index.
for (char ch : act) { /* ... */ }
Pointers should be kept simple, and a null pointer should be written as nullptr rather than 0 or NULL. The unsigned type should not be used for counts or indices; its use should be limited to situations that genuinely require bit-level manipulation.
FAQ
Q. What does if (auto n = v.size()) mean?
It is equivalent to checking whether the result is nonzero (n != 0), while also keeping the variable n scoped strictly to the conditional block, which is why it is recommended.
Q. Is it fine to declare a variable first and assign a value later, as in int n; n = v.size();?
This should be avoided, because an uninitialized declaration risks being read before the assignment happens, which leads to undefined behavior.
Q. Why shouldn't a comment simply say "assign y to x"?
What the code does is already visible by reading the code itself. A good comment should explain the intent that the code does not otherwise reveal -- in other words, why it is written that way.
Q. Why avoid unsigned for loop indices or counts?
Mixing signed and unsigned types in an operation can lead to unexpected conversions or values wrapping toward large numbers, so unsigned is recommended only when working directly with bits.
Sources
- Bjarne Stroustrup, A Tour of C++ (3rd ed.), §1.10 Advice
- C++ Core Guidelines
- cppreference — auto
- cppreference — range-based for loop
'C++' 카테고리의 다른 글
| C++ 1.7 포인터와 레퍼런스 정리: 주소, 배열, 참조, nullptr까지 (0) | 2026.09.19 |
|---|---|
| A Tour of C++ 1.9: 하드웨어 매핑과 포인터, 참조의 차이 (0) | 2026.09.18 |
| C++ std::atomic, 데이터 레이스를 막는 법 (0) | 2026.09.17 |
| C++ concepts, 템플릿 인자를 제약하는 법 (0) | 2026.09.15 |
| C++ 1.8 Tests 정리: 조건식, short-circuit, if 초기화, switch fall-through, vector::size() (0) | 2026.09.14 |
