일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- trie
- PrefixSum
- algorithm
- binary search
- 3D RPG
- Project
- SWEA
- Silver
- Euclidean
- two pointer
- Gold
- 프로그래머스
- LEVEL2
- 8-Puzzle
- 프로세스 상태
- BFS
- stack
- solid 원칙
- programmers
- Zenject
- BOJ
- Unity
- Modern C++
- Bronze
- Flyweight Pattern
- dirtyflag pattern
- level3
- knapsack Problem
- level1
- effective C++
- Today
- Total
Patrick's Devlog
[Effective C++] Chapter 1 정리 - 1 본문
개요
C++에 관련하여 조금 더 효과적이고 심도있게 공부하기 위해 관련 서적을 구매하여 공부할 예정이다. 서적은 C++를 조금 더 잘 설계하고 활용하고자 스콧 마이어스의 <Effective C++>를 선정하였다.
앞으로 정리해야할 부분은 블로그에 차근차근 작성할 예정이다.
Item 1 : C++를 언어들의 연합체로 바라보는 안목은 필수
C에서 파생된 C++는 초기와 다르게 점차 발전하여 다중패러다임 프로그래밍 언어(multiparadigm programming language)라고 불린다. 절차적, 객체지향적, 함수식, 일반화 프로그래밍을 포함해 메타프로그래밍 개념까지 지원한다.
위와 같은 유연함 덕분에 C++는 소프트웨어 개발에 매우 적절하다. 하지만 사용하기까지 C++의 이해도는 높아야 한다. C++를 조금 더 잘 이해하기 위해 단일 언어로 바라보는 것 뿐만 아니라, 여러 언어들의 연합체로 보면 된다. 여러 언어들은 아래의 네 가지라고 말할 수 있다.
- C
- 객체지향 개념의 C++
- 템플릿 C++
- STL
여기서 효과적인 프로그램 개발을 위해 한 하위 언어에서 다른 하위 언어로 옮겨가면서 대응 전략을 바꾸어야 하는 상황에도 당황하지 말아야 한다.
그리고 공부를 시작하기 전 C++는 하위 언어들로 구성되어 있다는 점을 새기고 진행해야 한다.
Item 2 : #define을 쓰려거든 const, enum, inline을 떠올리자
#define ASPECT_RATIO 1.653
우리는 ASPECT_RATIO가 symbolic name으로 보이지만 컴파일러에겐 전혀 보이지 않는다. 소스 코드가 컴파일러에게 넘어가기 전에 선행 처리자가 밀어버리고 숫자 상수로 바꾸어버리기 때문이다. 그 결과 위의 이름은 컴파일러가 쓰는 기호테이블에 들어가지 않는다. 따라서 오류가 발생했을 때 위의 정의된 이름이 아닌, 1.653으로 나와 있을 것이다.
위의 문제의 해결법은 아래의 상수를 대신 사용하는 것이다.
const double AspectRatio = 1.653;
AspectRatio는 언어 차원에서 지원하는 상수타입 데이터이므로 당연히 컴파일러 눈에도 보이고 기호 테이블에도 저장된다. #define을 썼을 때보다 최종 코드의 크기가 더 적게나온다. #define은 여러번 쓰였을 때 1.653의 사본이 여러 번 들어가지만, 상수타입은 여러번 쓰였을 때 사본은 딱 하나만 생긴다.
위의 과정과 같이 #define을 상수로 교체할 때 두 가지 경우만 조심하면 된다.
1. 상수 포인터(constant pointer)를 정의
상수 정의는 대게 헤더 파일에 넣는 것이 상례이므로 포인터는 꼭 const로 선언해주고, 포인터가 가리키는 대상까지 const로 선언하는 것이 보통이다.
const char * const authorName = "Scott Meyers";
헤더 파일 안에 char * 기반 문자열 상수를 정의하면 위와 같이 작성해야 한다. char * 기반의 문자열 보다는 string 객체가 대체적으로 사용하기 좋으므로 아래의 방법처럼 정의하는 것이 더 좋다.
const std::string authorName("Scott Meyers");
2. 클래스 멤버로 상수 정의
어떤 상수의 유효 범위를 클래스로 한정하고자 할때 멤버로 만들 상수의 사본 개수가 한 개를 초과하지 않게 하고 싶다면 static으로 생성해야 한다.
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns];
...
};
이 때 NumTurns는 선언(declaration)된 것이다. 보통 사용하고자 하는 것에 대해서는 정의가 필요하지만 정적 멤버로 만들어지는 정수 타입의 클래스 내부 상수는 예외이다. 주소를 취하지 않는 한 선언만 진행해도 문제없다.
단, 클래스 상수의 주소를 구하거나 주소를 구하지 않는데도 컴파일러가 잘못 만들어진 관계로 정의를 달라고 하는 경우에는 별도로 정의를 제공해야 한다.
const int GamePlayer::NumTurns; //NumTurns의 정의
이 때 클래스 상수 정의는 구현 파일에 둬야한다. 정의에는 상수 초기값이 있으면 안되는데, 클래스 상수의 초기값은 해당 상수가 선언된 시점에서 바로 주어지기 때문이다.
조금 오래된 컴파일러는 위 문법을 받아들이지 않는 경우가 존재한다. 정적 클래스 멤버가 선언된 시점에 초기값을 주는 것이 대개 맞지 않다고 판단하기 때문이다.
class CostEstimate{
private:
static const double FudgeFactor; //정적 클래스 상수의 선언, 헤더 파일에 두면 됨
...
};
const double CostEstimate::FudgeFactor = 1.35f; //정적 클래스 상수의 정의, 구현 파일에 두면 됨
그럴 경우는 위의 코드로 대처하면 된다. 한 가지 예외가 있다면 해당 클래스를 컴파일하는 도중 클래스 상수의 값이 필요할 때이다. 정수 타입의 정적 클래스 상수에 대한 클래스 내 초기화를 금지하는 구식 컴파일러에 대한 배려로서 방법을 추천한다면 나열자 둔갑술(enum hack)이 있다. 이 기법의 원리는 enumerator 타입의 값은 int가 놓일 곳에도 쓸수 있다는 C++의 진실을 적극 활용하는 것이다.
class GamePlayer{
private:
enum { NumTurns = 5 };
int scores[NumTurns];
...
};
이 나열자 둔갑술을 알아두는 것이 여러 가지 이유로 도움이 될 것이다.
1. 나열자 둔갑술은 동작 방식이 const보다는 #define에 가깝다.
선언한 정수 상수를 가지고 다른 사람이 주소를 얻는다든지, 참조자를 쓴다든지 하는 것이 싫으면 enum은 좋은 방법이 될 것이다. 또한 제대로 만들어진 컴파일러는 const 객체에 대해 저장공간을 준비하지 않겠지만, 덜 만들어진 컴파일러는 반대로 동작할 수 있으므로 양쪽 모두에 대해 안전하게 const 객체에 대한 메모리를 만들지 않는 방법을 쓰고싶을 것이다. enum은 #define처럼 어떤 형태의 쓸데없는 메모리 할당도 절대 저지르지 않는다.
2. 상당히 많은 코드에 이 기법이 쓰이고 있으므로, 혹시 발견하면 쉽게 알아보도록 눈을 단련 시켜야 한다.
상당히 많은 경우에서 발견되는 #define 지시자의 다른 오용 사례는 매크로 함수이다. 함수처럼 보이지만, 함수 호출 오버헤드를 일으키지 않는 매크로를 구현하는 것이다.
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
위의 매크로는 단점이 많은 코드이다. 이러한 매크로를 작성할 때는 매크로 본문에 들어있는 인자마자 반드시 괄호를 씌워주어야 한다. 이게 안되어 있으면, 표현식을 매크로로 넘길 때 문제가 발생할 수 있다.
int a = 5, b = 0;
CALL_WITH_MAX(++a ,b);
CALL_WITH_MAX(++a, b+10);
f가 호출되기 전 a가 증가하는 횟수가 달라진다. 비교를 통해 처리한 결과가 어떤 것이냐에 따라 달라진다.
C++에서는 함수 호출을 없애준다는 명목에 진행되는 이러한 상황을 굳이 만들 필요가 없다. 기존 매크로 효율을 그대로 유지하고, 정규 함수의 모든 동작 방식 및 타입 안전성까지 완벽히 취할 수 있는 방법이 존재한다. 아래의 코드처럼 인라인 함수에 대한 템플릿을 준비하면 된다.
template<typename T>
inline void callWithmax(const T& a, const T& B) {
f(a > b ? a : b);
}
이 함수는 템플릿이므로 동일 계열 함수군(family of functions)을 만들어 낸다. 동일한 타입의 객체 두 개를 인자로 받고 둘 중 큰 것을 f에 넘겨 호출하는 구조이다. 함수 본문에 괄호로 할 필요 없으며, 인자를 여러번 평가하지 않는다. 그 뿐만 아니라 위의 코드는 진짜 함수이므로 유효범위 및 접근 규칙을 그대로 따라간다.
★ 단순한 상수를 쓸 때 #define보다 const or enum을 우선 생각하자
★ 함수처럼 쓰이는 매크로를 만드려면, #define보다 인라인 함수를 우선으로 생각하자
'Programming Language > C++' 카테고리의 다른 글
[Effective C++] Chapter 2 정리 - 1 (0) | 2022.05.26 |
---|---|
[Modern C++] Lvalue 및 Rvalue (0) | 2022.05.20 |
[Modern C++] Smart Pointer (0) | 2022.05.17 |
[Modern C++] 시작하기 (0) | 2022.05.16 |
[Effective C++] Chapter 1 정리 - 2 (0) | 2022.05.11 |