Item1: C++을 언어의 연합(federation) 으로 보기
개요
현대 C++은 Procedural, Object-Oriented, Functional, Generic, Metaprogramming 같은 프로그래밍 패러다임이 혼합되어 있다.
이는 유연함을 주면서 혼동을 주기도 한다.
따라서 C++을 하나의 언어가 아니라 여러 개의 관련 언어가 연합된 형태로 바라보는 것이 이해하기 쉽다.
C++의 서브 언어는 4가지 분류가 있다.
서브 언어
1. C스타일의 C++
- C에서 사용하던 블록 구조, 문장, 전처리기, 기본, 데이터 타입, 배열, 포인터 등을 그대로 사용한다.
- C++은 C보다 더 나은 해결책을 제시한다. (뒤에 나온다고 함)
2. 객체지향 C++
- 클래스, 생성자, 소멸자, 캡슐화, 상속, 다형성, 가상 함수(동적 바인딩) 등의 개념 포함
3. 템플릿 기반 C++
4. STL: STL은 특수한 템플릿 라이브러리이다. STL만의 독특한 규칙을 따르는 것이 중요하다.
| 서브언어 | 주요 개념 | 사용 예시 |
| C 스타일 C++ | 기본 데이터 타입, 포인터, printf() | char* 기반 문자열 처리, C 함수 사용 |
| 객체지향 C++ | 클래스, 상속, 다형성, 가상 함수 | class, public/private, override |
| 템플릿 C++ | 제너릭 프로그래밍, 템플릿 메타프로그래밍 | template <typename T>, TMP 활용 |
| STL | 컨테이너, 알고리즘, 이터레이터 | std::vector, std::sort(), std::map |
각 서브 언어를 선택하는 전략
예를 들어
- C 스타일 C++에서는 pass by value가 내장 타입에서 더 효율적이다.
- 객체지향 C++에서는 생성자, 소멸자로 인해 pass by reference to const가 더 낫다.
- 템플릿 C++에서는 객체의 타입을 바로 알 수 없어 pass by reference to const가 더 중요해진다.
- STL의 iterator, functor는 C스타일 포인터 기반으로 동작해, pass by value가 적절한 경우가 많다.
결론: 효율적인 C++ 프로그래밍 규칙은 C++의 어느 부분을 사용하는 지에 따라 달라진다.(특정 규칙을 맹목적으로 적용하지 말고 상황에 맞게 판단하자)
Item2: #defines대신 const, enum, inline 쓰기
#define의 문제
1. #define은 전처리기가 값을 치환하고 컴파일러로 전달하기 때문에 컴파일러는 기존 선언 이름을 모른다.
#define ASPECT_RATIO 1.653
ASPECT_RATIO라는 이름을 모른다.
2. #define으로 정의된 값은 symbol table에 들어가지 않아 디버거에서 값의 출처를 알 수 없다.
3. 컴파일된 바이너리에서 여러 개의 치환된 값이 존재할 수 있다.
따라서 const를 사용하자!
컴파일러가 값을 symbol table에 저장하게되어 디버깅이 쉬워진다.
#define -> const에서 주의할 점
1. 상수를 가리키는 포인터 정의 시
const char * const authorName = "Scott Meyers";
포인터가 가리키는 값과 포인터 자체를 모두 const로 선언한다.
첫 번째 const -> 포인터가 가리키는 값
두 번째 const -> 포인터 자체
물론 char*를 string으로 대체 하는것이 좋다.
2. 클래스 내부에서 사용되는 상수
class GamePlayer {
private:
static const int NumTurns = 5; // 상수 선언
int scores[NumTurns]; // 상수 사용
...
};
static 으로 사용 시 객체를 생성할 때마다 만들어지는 것이 아니라, 클래스 전체에서 단 하나의 복사본을 유지한다.
클래스 상수는 #define 불가능!
#define은 scope개념이 없어서 클래스 내부에서 한정적으로 사용하는 것이 불가능하다.
Enum Hack
static const int 멤버 초기화 시에, 일부 컴파일러에서 오류가 발생할 수 있다고 한다.
이유는 static const int값을 컴파일 타임 상수로 처리하지 못해서다.
이를 위해 enum으로 우회하는 방법을 사용한다고 한다.
class GamePlayer {
private:
enum { NumTurns = 5 }; // "Enum Hack" - NumTurns를 5의 심볼릭 이름으로 만듦
int scores[NumTurns]; // 정상적으로 컴파일됨
};
Enum Hack은?
- 메모리를 차지하지 않는다.
- #define 보다 안전하다.
- 참조를 얻을 수 없다.
- 템플릿 메타프로그래밍에서 유용하다.
최신 C++에서는(C++11이상) constexpr이 도입되어 Enum Hack 안써도 됨
#define을 사용한 매크로 기반 함수의 문제점
1.연산자 우선순위 문제: 전처리기 매크로는 단순한 텍스트 치환이기 때문에 연산자 우선순위 문제가 생긴다.
#define SQUARE(x) x * x
int a = 5;
int b = SQUARE(a + 1); // 기대값: (6 * 6) = 36
SQUARE(a + 1)는 a + 1 * a + 1로 치환되어 6 + 6 + 1 = 13이 된다.
2. Multiple Evaluation
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a가 두 번 증가됨!
CALL_WITH_MAX(++a, b+10); // a가 한 번만 증가됨
a가 ++a가 되기 때문에 만약 a가 더 큰 경우 a가 두 번 증가한다.
인라인 함수를 통한 해결
template<typename T>
inline void callWithMax(const T& a, const T& b) {
f(a > b ? a : b);
}
인라인 함수는 타입체크가 가능하고 단순 치환이 아니기 때문에 #define에 완전한 대체재이다.
'C, C++ > Effective C++' 카테고리의 다른 글
| 4장 설계와 선언 (0) | 2025.03.21 |
|---|---|
| 3장 리소스 관리 (0) | 2025.03.21 |
| 2장 생성자, 소멸자 그리고 할당 연산자 (2) (0) | 2025.03.17 |
| 2장 생성자, 소멸자 그리고 할당 연산자 (1) (0) | 2025.03.16 |
| 1장 C++과 친해지기 (2) (0) | 2025.03.15 |