서적 '이펙티브 C++'을 공부하면서 개인적인 복습을 목적으로 기록하는 포스팅입니다.
책 전문은 포함되어 있지 않으며, 책을 학습하다가 궁금했던 내용 위주로 보충 공부한 것을 주로 다룹니다.
1. C++을 언어들의 연합체로 바라보는 안목은 필수
[요약]
멀티패러다임 언어로서의 C++ 개념
- 초창기의 C++은 C언어를 기반으로 객체 지향 기능 몇가지가 결합된 형태의, '클래스를 쓰는 C'였습니다.
- 그러나 이후 C++은 현재까지도 계속해서 새로운 문법이 추가되며 확장 및 발전되고 있습니다.
- 그렇기 때문에 오늘날의 C++은 다중패러다임(=멀티패러다임) 프로그래밍 언어라고 불립니다.
- 즉, C++은 절차적 프로그래밍을 기본으로 객체 지향, 함수식, 일반화 프로그래밍을 포함한 메타프로그래밍 개념까지 지원합니다.
- 따라서 C++은 단일 언어라기 보다는, 상관 관계가 있는 여러 하위 언어들을 제공하는 일종의 연합체라고도 할 수 있습니다.
- 여기서 하위 언어에 해당하는 것들은 총 4가지로 C, 객체 지향 개념의 C++, 템플릿 C++, STL이 있습니다.
- [이 관점이 중요한 이유: C++의 어떤 부분을 사용하는지, 그 상황에 따라 변하는 '최적의 법칙']
C++를 연합체로 바라봐야 하는 가장 큰 이유는, 각각의 하위 언어마다 효율적인 코드를 작성하는 규칙(rule of thumb)이 달라지기 때문입니다. 예를 들어, 함수의 '매개변수를 전달하는 가장 좋은 방법'이라는 단순한 주제조차도 어떤 하위 언어의 관점에서 보느냐에 따라 답이 달라집니다.
- C 언어 관점에서
: struct 같은 사용자 정의 타입이라도 크기가 작다면 값으로 전달(pass-by-value)하는 것이 효율적일 때가 많습니다. 포인터를 쓰는 것보다 단순하고 캐시 친화적일 수 있죠.
- 객체 지향 C++ 관점에서
: 상속과 다형성이 적용된 복잡한 클래스 객체는 생성/소멸 비용이 크기 때문에, 상수 참조자로 전달(pass-by-reference-to-const)하는 것이 거의 모든 경우에 일반적인 규칙이 됩니다. 불필요한 객체의 복사를 막기 위함이죠.
- 템플릿 C++ 관점에서
: 상황이 또 달라집니다. 다양한 타입이 들어올 수 있는 템플릿 코드에서는, 컴파일러의 최적화 방식과 맞물려 오히려 값으로 전달(pass-by-value)하는 것이 더 효율적인 선택지가 될 수 있습니다. (이는 나중에 배울 '이동 의미론'과도 깊은 관련이 있습니다.)
- STL 관점에서
: 이터레이터(iterator)와 함수 객체(function object)를 값으로 전달하는 것이 STL의 일반적인 관례입니다. 포인터처럼 동작하지만 포인터는 아닌, STL만의 독자적인 규칙을 따르는 것이 중요합니다.
2. #define을 쓰려거든 const, enum, inline을 떠올리자
[요약]
- #define(매크로)은 선행 처리자(Preprocessor)에 해당됩니다.
- 그러나 선행 처리자는 컴파일러가 소스 코드를 본격적으로 컴파일하기 전에 단순 치환으로 동작합니다.
- 즉, 컴파일러가 관리하는 심볼 테이블에 등록되지 않아 타입 검사와 최적화, 변수명 충돌 방지 등을 제대로 처리할 수 없습니다.
- 이러한 문제를 해결하기 위해서 컴파일러가 이해할 수 있는 방식을 사용하는 것이 좋습니다.
- 목적에 따른 #define의 대안은 아래와 같습니다.
목적 | #define 대안 | 이유 |
상수 정의 | const / constexpr | 타입 정보 + 컴파일 타임 검사 가능 |
열거 값 정의 | enum / enum class | 네임스페이스 분리 및 정수형 연산 안전 |
간단한 매크로 함수 | inline 함수 | 타입 안전 + 디버깅 쉬움 + 매개변수 평가 문제 없음 |