Effective C++ 3판을 기반으로 작성한 글입니다.
C/C++
- 선언과 정의, 초기화
- 선언은 코드에 사용되는 '어떤 대상'의 이름과 타입을 컴파일러에게 알려주는 것
=> 구체적인 세부사항은 선언에 들어 있지 않다.
- 정의는 선언에서 빠진 구체적인 세부사항을 컴파일러에게 제공하는 것
=> 객체의 경우 컴파일러가 그 객체에 대한 메모리를 마련해 놓는 부분
=> 함수나 함수 템플릿에 대한 정의는 그들에 대한 코드 본문(body)을 제공하는 것
=> 클래스 혹은 클래스 템플릿의 경우 그 클래스 혹은 템플릿의 멤버를 넣어 준 결과
- 초기화는 어떤 객체에 최초의 값을 부여하는 과정
=> 사용자 정의 타입으로 생성한 객체의 경으, 초기화는 생성자에 의해 이루어진다.
- 선언과 정의의 가장 큰 차이는 메모리 할당 여부이다.
=> 메모리를 할당하지 않고, 대상의 이름만 알려준다면 선언이고 대상의 메모리가 할당된다면 정의이다.
- 항목 1: C++를 언어들의 연합체로 바라보는 안목은 필수
- C++를 단일 언어로 바라보는 눈을 넓혀, 상관 관계가 있는 여러 언어들의 연합체(federation)로 생각하자.
- 하위 언어
1. C
: C++는 C를 기본으로 한다.
2. 객체 지향 개념의 C++
: '클래스를 쓰는 C'에 관한 모든 것
=> 객체 지향 설계의 규칙들이 대부분 그대로 들어맞는 부분
3. 템플릿 C++
: C++의 일반화 프로그래밍 부분
4. STL
: 템플릿 라이브러리
=> STL 나름의 독특한 사용규약이 있어서, STL을 써서 프로그래밍하려면 그 규약을 따르면 된다.
☆ C++를 사용한 효과적인 프로그래밍 규칙은 경우에 따라 달라진다. 그 경우란, 바로 C++의 어떤 부분(하위 언어 중 어떤 것)을 사용하느냐이다. ☆
- 항목 2: #define을 쓰려거든 const, enum, inline을 떠올리자
- 가급적 선행 처리자보다 컴파일러를 더 가까이 하자
- #define을 이용해서 매크로를 사용하면 선행 처리자가 컴파일러에게 넘어가기 전 해당 기호식 이름을 숫자 상수로 바꾸어 버린다.
=> 기호식 디버거(symbolic debugger)에서도 같은 문제가 발생할 수 있다.
=> 숫자 상수로 대체된 코드에서 컴파일 에러가 발생하면 헷갈릴 수 있다.
=> 매크로 대신 상수를 사용하면 해결된다.
ex) #define ASPECT_RATIO 1.653 => const double AspectRatio = 1.653;
- 상수가 부동소수점 실수 타입일 경우 상수를 사용하면 #define을 썼을 때보다 최종 코드의 크기가 더 작게 나올 수 있다.
=> #define은 실수 값의 사본이 등장 횟수만큼 들어가지만 상수 타입은 아무리 여러 번 사용돼도 사본은 딱 한 개만 생기기 때문
- #define 대신 상수로 교체할 경우 주의사항
1. 상수 포인터(const pointer)를 정의하는 경우
=> 상수 정의는 대개 헤더 파일에 넣는 것이 상례이다.
=> 포인터와 포인터가 가리키는 대상까지 모두 const로 선언해주는 것이 보통이다.
ex) const char* const authorName = "Scott Meyers";
2. 클래스 멤버로 상수를 정의하는 경우, 즉 클래스 상수를 정의하는 경우
=> 어떤 상수의 유효범위를 클래스로 한정하고자 할 때는 그 상수를 멤버로 만들어야 하며, 상수의 사본 개수가 한 개를 넘지 못하게 하고 싶다면 정적(static) 멤버로 만들어야 한다.
- C++에서 사용하고자 하는 것에 대해 '정의'가 마련되어 있어야 하는게 보통이지만, 정적 멤버로 만들어지는 정수류 타입의 클래스 내부 상수는 예외이다.
=> 이들에 대해 주소를 취하지 않는 한, 정의 없이 선언만 해도 아무런 문제가 없다.
=> 클래스 상수의 정의에는 상수의 초기값이 있으면 안 된다.
==> 클래스 상수의 초기값은 해당 상수가 선언된 시점에서 바로 주어지기 때문
=> 정적 클래스 상수의 선언은 헤더 파일, 정의는 구현 파일에 들어간다.
- #define은 클래스 상수를 정의하는 데 쓸 수 없으며 어떤 형태의 캡슐화 혜택도 받을 수 없다.
- 정적 클래스 상수의 선언 시점에 초기화 하는 것이 허용되지 않는 몇몇 컴파일러의 경우 초기값을 상수의 '정의' 시점에 주면 된다.
=> 혹은 '나열자 둔갑술(enum hack)'이라는 기법을 생각할 수도 있다.
==> 나열자(enumerator) 타입의 값은 int가 놓일 곳에도 쓸 수 있다는 점을 활용하는 것이다.
- 나열자 둔갑술은 동작 방식이 const보다 #define에 더 가깝다.
=> const의 주소를 요구하는 것은 합당하지만, enum의 주소를 취하는 일은 불법이다.(#define의 주소를 얻는 것 역시 마찬가지로 불법)
=> 선언한 정수 상수를 가지고 다른 사람이 주소를 얻는다든지 참조자를 쓴다든지 하는 것이 싫다면 enum이 좋은 자물쇠 역할이 된다.
- enum은 #define처럼 어떤 형태의 쓸데없는 메모리 할당이 전혀 없다.
- 나열자 둔갑술은 상당히 많은 코드에서 사용되고 있으므로 이런 것을 발견하면 쉽게 알아볼 수 있도록 눈을 단련시켜 두는 것이 좋다.
=> 나열자 둔갑술은 템플릿 메타 프로그래밍(TMP)의 핵심 기법이기도 하다.
- 매크로 함수의 단점
1. 매크로 본문에 들어있는 인자마다 반드시 괄호를 씌워 줘야 한다.
2. 비교 경우에 따라 처리한 결과가 달라질 수 있다.
ex)
#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가 한 번 증가한다.
- 매크로 함수 대신 인라인 함수에 대한 템플릿를 준비하면 매크로의 효율을 그대로 유지하며 정규 함수의 모든 동작방식 및 타입 안전성까지 완벽히 취할 수 있다.
- const, enum, inline의 사용으로 선행 처리자(특히 #define)를 꼭 써야하는 경우를 많이 줄일 수는 있지만, 현실적으로 완전히 뿌리 뽑기는 힘들다.
=> #include는 부동의 필수 요소이다.
=> #ifdef/#ifndef도 컴파일 조정 기능으로 현장에서 유용하게 사용된다.
☆ 단순한 상수를 쓸 때는, #define보다 const 객체 혹은 enum을 우선 생각하기.
☆ 함수처럼 쓰이는 매크로를 만들려면, #define 매크로보다 인라인 함수를 우선 생각하기.
※ 동일 계열 함수군(family of functions) : 하나의 템플릿을 통해 만들어질 것으로 예측 가능한 모든 함수를 통칭한다.
'C,C++ > 정보정리' 카테고리의 다른 글
[C/C++] C++에 왔으면 C++의 법을 따릅시다 - (2) (0) | 2023.04.10 |
---|---|
[C/C++] Thread - (1) (0) | 2023.02.15 |
[C/C++] Callable (0) | 2023.02.11 |
[C/C++] 자원 관리(feat. 스마트 포인터) (0) | 2023.02.10 |
[C/C++] Lvalue, Rvalue (0) | 2023.02.10 |
댓글